简体   繁体   中英

How to fit variable length tick labels on a D3 line chart?

Here's a JSFiddle: http://jsfiddle.net/8p2yc/ (A slightly modified example from here: http://bl.ocks.org/mbostock/3883245 )

As you can see in the JSFiddle tick labels along the y axis do not fit in the svg. I know I can increase the left margin, but the thing is I don't know what the data will be in advance. If I just make the margin very large the chart will look awkward if the numbers are short in length.

Is there a way to precompute the maximum label width when creating the chart to set the margin correctly? Or perhaps there's an entirely different solution?

var margin = {top: 20, right: 20, bottom: 30, left: 50},
    width = 400 - margin.left - margin.right,
    height = 200 - margin.top - margin.bottom;

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

示例图表

Thanks!

You can do this by appending the text for the largest label, measuring it and removing it immediately afterwards:

var maxLabel = d3.max(data, function(d) { return d.close; }),
    maxWidth;
svg.append("text").text(maxLabel)
   .each(function() { maxWidth = this.getBBox().width; })
   .remove();

Then you can use that width to do the translation of the g element:

svg.attr("transform", "translate(" + Math.max(margin.left, maxWidth) + "," + margin.top + ")");

Complete example here .

Edit: Getting the maximum length of the actual labels is a bit more involved because you have to generate them (with the right formatting) and measure them all. This is a better way to do it though as you're measuring what's actually displayed. The code is similar:

var maxWidth = 0;
svg.selectAll("text.foo").data(y.ticks())
   .enter().append("text").text(function(d) { return y.tickFormat()(d); })
   .each(function(d) {
     maxWidth = Math.max(this.getBBox().width + yAxis.tickSize() + yAxis.tickPadding(), maxWidth);
   })
 .remove();

I'm adding the size of the tick line and the padding between tick line and label to the width here. Complete example of that here .

A chicken-and-egg problem prevents this from being precomputed reliably, assuming you want your axes and plot to combine to fill a fixed space, and your tick labels to be generated automatically.

The problem is that the tick labels, and therefore the space they require, depend on the scale. A time scale, for example, may include longer month names (eg 'September') over a short domain, but shorter month names or just years over a long domain. But the scale, at least on the opposite axis, depends on the space left for the range, which depends on the space left over after determining the tick labels.

In a lot of cases, the minimum and maximum values may give you an idea of the widest tick labels, and you can use Lars' method . For example, if the domain is –50 to 10, any tick labels between those will be narrower. But this assumes they're integers! Also, be careful with ' nice ' scales; if your maximum value is 8, D3 may try to make the greatest tick label 10, which is a wider string.

d3 v4 Solution for Lars' Method.

calculateMarginForYScaleTicks() {
    let maxWidth = 0;
    let textFormatter = d3.format(",");
    let tempYScale = d3
        .scaleLinear()
        .range([0, 0])
        .domain([0, d3.max(data, d => d.value)]);

    d3
        .select("#svg")
        .selectAll("text.foo")
        .data(tempYScale.ticks())
        .enter()
        .append("text")
        .text(d => textFormatter(d))
        .each(function() {
            maxWidth = Math.max(this.getBBox().width, maxWidth);
        })
        .remove();

    return maxWidth;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM