Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

example chart

Thanks!

like image 784
vrepsys Avatar asked Feb 06 '14 13:02

vrepsys


2 Answers

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.

like image 125
Lars Kotthoff Avatar answered Oct 20 '22 16:10

Lars Kotthoff


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;
}
like image 39
Athif Shaffy Avatar answered Oct 20 '22 16:10

Athif Shaffy