Equivalent of d3 scale.bandwidth() for a linear scale's ticks?

Viewed 2494

In D3, you can return the width between two bands with band.bandwidth().

Is there an equivalent to this for d3.scaleLinear() for your given domain, provided you've set the ticks on an axis?

So if your tickValues domain is [-2,-1,0,1,2], scaleLinear.tickWidth() would provide you with the difference between scaleLinear(0) and scaleLinear(1) (or scaleLinear(-2) and scaleLinear(-1)), or whatever you've set the difference between the ticks to be.

1 Answers

As you know, there is no such method. Also, the very concept of a bandwidth (which carries no information) for a linear scale makes little sense.

But the interesting think about your question is that it's not about the linear scale per se, it is about the axis generated using that scale (as you said "...provided you've set the ticks on an axis").

In D3 the axis generated when we use a scale is quite unpredictable (that is, the number of ticks and their values), specially when using a time scale. Besides that, you can change the ticks using axis.ticks() or axis.tickArguments(). Because of this, using scale.ticks() to get the values of the ticks is not an accurate method.

That being said, you can use a function to which you pass the axis group itself (an SVG <g> element), like this one I just wrote:

function tickWidth(selection) {
    const ticks = selection.selectAll(".tick text")
        .nodes()
        .map(function(d) {
            return +d.textContent;
        });
    return scale(ticks[1]) - scale(ticks[0]);
}

What it does is basically getting all <text> elements inside the .ticks groups, converting them to numbers (they are strings) and returning the difference in the scale.

Here is a demo:

const svg = d3.select("svg");
const scale = d3.scaleLinear()
  .range([50, 450])
  .domain([0, 100]);
const axis = d3.axisBottom(scale);
const axisGroup = svg.append("g")
  .attr("transform", "translate(0,50)")
  .call(axis);

const bandwidth = tickWidth(axisGroup);

console.log("the bandwidth is: " + bandwidth + " pixels")

function tickWidth(selection) {
  const ticks = selection.selectAll(".tick text").nodes().map(function(d) {
    return +d.textContent;
  });
  return scale(ticks[1]) - scale(ticks[0]);
}
<svg width="500" height="100"></svg>
<script src="https://d3js.org/d3.v5.min.js"></script>

As you can see, this approach takes into account methods like axis.ticks(), which modify the ticks:

const svg = d3.select("svg");
const scale = d3.scaleLinear()
  .range([50, 450])
  .domain([0, 100]);
const axis = d3.axisBottom(scale)
  .ticks(5);
const axisGroup = svg.append("g")
  .attr("transform", "translate(0,50)")
  .call(axis);

const bandwidth = tickWidth(axisGroup);

console.log("the bandwidth is: " + bandwidth + " pixels")

function tickWidth(selection) {
  const ticks = selection.selectAll(".tick text").nodes().map(function(d) {
    return +d.textContent;
  });
  return scale(ticks[1]) - scale(ticks[0]);
}
<svg width="500" height="100"></svg>
<script src="https://d3js.org/d3.v5.min.js"></script>

Related