You're probably better off passing in all the labels at once -- this is also more in line with the general d3 idea. You could then have code something like this:

   .attr("transform", function(d, i) {
       var currenty = y(d.value.temperature);
       if(i > 0) {
           var previousy = y(data[i-1].value.temperature),
           if(currenty - previousy < 12) { currenty = previousy + 12; }
       return "translate(" + x( + "," +  currenty + ")";
   .attr("x", 3)
   .attr("dy", ".35em")
   .text(function(d) { return; });

This does not account for the fact that the previous label may have been moved. You could get the position of the previous label explicitly and move the current one depending on that. The code would be almost the same except that you would need to save a reference to the current element (this) such that it can be accessed later.

All of this will not prevent the labels from being potentially quite far apart from the lines they are labelling in the end. If you need to move every label, the last one will be pretty far away. A better course of action may be to create a legend separately where you can space labels and lines as necessary.


Consider using a D3 force layout to place the labels. See an example here:

Assuming you have a data array containing objects with a value property, and a scale y:

// Create some nodes
const labels = => {
  return {
    fx: 0,
    targetY: y(d.value)

// Set up the force simulation
const force = d3.forceSimulation()
  .force('collide', d3.forceCollide(10))
  .force('y', d3.forceY(d => d.targetY).strength(1))    

// Execute thte simulation
for (let i = 0; i < 300; i++) force.tick();

// Assign values to the appropriate marker
labels.sort((a, b) => a.y - b.y);
data.sort((a, b) => b.value - a.value);
data.forEach((d, i) => d.y = labels[i].y);

Now your data array will have a y property representing its optimal position.

Example uses D3 4.0, read more here:

Related Query

More Query from same tag