Shane G Shane G - 3 years ago 150
Javascript Question

D3 incorrect drag behaviour

This is a follow on from my previous question
d3 rect in one group interfering with rect in another group

Two issues:


  1. Incorrect drag behaviour. When clicking on the second rect to drag it, it jumps to where the third one is.

  2. I added a anonymous function which runs when the svg in clicked on anywhere. This should add a new rect. However that is the working.



I know I should have only one issue per question but these are related and I suspect they will be solved together.



<!DOCTYPE html>
<meta charset="utf-8">
<style>
/*.active {
stroke: #000;
stroke-width: 2px;
}*/
</style>
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 20
},
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;

var svg = d3.select("svg");

var data = [{
x: 200
}, {
x: 300
}, {
x: 400
}];

var groove = svg.append("g")
.attr("class", "groove_group");

groove.append("rect")
.attr("x", 100)
.attr("y", 150)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 6)
.attr("width", 800)
.style("fill", "grey");

groove.append("rect")
.attr("x", 102)
.attr("y", 152)
.attr("rx", 2)
.attr("ry", 2)
.attr("height", 2)
.attr("width", 796)
.style("fill", "black");

// create group
var group = svg.selectAll(null)
.data(data)
.enter().append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);

group.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 100)
.attr("height", 100)
.attr("width", 15)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'handle_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");

group.append("text")
.attr("x", function(d) {
return d.x
})
.attr("y", 100)
.attr("text-anchor", "start")
.style("fill", "black")
.text(function(d) {
return "x:" + d.x
});

// create group
var group = svg.selectAll("g")
.data(data)
.enter().append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", removeElement);

group.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "lightblue")
.attr('id', function(d, i) {
return 'handle_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");

group.append("text")
.attr("x", function(d) {
return d.x
})
.attr("y", 200)
.attr("text-anchor", "start")
.style("fill", "black")
.text(function(d) {
return "x:" + d.x
});

svg.on("click", function() {
var coords = d3.mouse(this);

var newData = {
x: d3.event.x,
}
data.push(newData);

group.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
return d.x;
})
.attr("y", 200)
.attr("height", 100)
.attr("width", 15)
.style("fill", "steelblue")
.attr('id', function(d, i) {
return 'rect_' + i;
})
.attr("rx", 6)
.attr("ry", 6)
.attr("stroke-width", 2)
.attr("stroke", "black");
});


function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}

function dragged(d) {
d3.select(this).select("text")
.attr("x", d.x = d3.event.x);
d3.select(this).select("rect")
.attr("x", d.x = d3.event.x);
}

function dragended(d) {
d3.select(this)
.classed("active", false);
}

function removeElement(d) {
d3.event.stopPropagation();
data = data.filter(function(e) {
return e != d;
});
d3.select(this)
.remove();
}
</script>




Answer Source

Here are the explanations to your issues:

  1. You are reassigning var groups, that is, you have two var groups in your code, the last one overwriting the first one. Just remove the last variable.
  2. In your function to append new rectangles, you are using an update selection that selects rectangles. However, your enter selection appends groups (<g>) elements, not rectangles.

Have a look at the refactored function, it binds the data to a newly created group and appends the rectangle to that group:

var newGroup = svg.selectAll(".group")
    .data(data, function(d) {
        return d.x
    })
    .enter()
    .append("g")
    //etc...

newGroup.append("rect")
    //etc...

Also, use a key selection in the data binding, so you know exactly what rectangle is being dragged:

.data(data, function(d){return d.x})

Here is your code with those changes:

<!DOCTYPE html>
<meta charset="utf-8">
<style>
  /*.active {
    stroke: #000;
    stroke-width: 2px;
  }*/
</style>
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
  var margin = {
      top: 20,
      right: 20,
      bottom: 20,
      left: 20
    },
    width = 600 - margin.left - margin.right,
    height = 600 - margin.top - margin.bottom;

  var svg = d3.select("svg");

  var data = [{
    x: 200
  }, {
    x: 300
  }, {
    x: 400
  }];

  var groove = svg.append("g")
    .attr("class", "groove_group");

  groove.append("rect")
    .attr("x", 100)
    .attr("y", 150)
    .attr("rx", 2)
    .attr("ry", 2)
    .attr("height", 6)
    .attr("width", 800)
    .style("fill", "grey");

  groove.append("rect")
    .attr("x", 102)
    .attr("y", 152)
    .attr("rx", 2)
    .attr("ry", 2)
    .attr("height", 2)
    .attr("width", 796)
    .style("fill", "black");

  // create group
  var group = svg.selectAll(null)
    .data(data, function(d){return d.x})
    .enter().append("g")
    .attr("class", "group")
    .call(d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended))
    .on("click", removeElement);

  group.append("rect")
    .attr("x", function(d) {
      return d.x;
    })
    .attr("y", 100)
    .attr("height", 100)
    .attr("width", 15)
    .style("fill", "lightblue")
    .attr('id', function(d, i) {
      return 'handle_' + i;
    })
    .attr("rx", 6)
    .attr("ry", 6)
    .attr("stroke-width", 2)
    .attr("stroke", "black");

  group.append("text")
    .attr("x", function(d) {
      return d.x
    })
    .attr("y", 100)
    .attr("text-anchor", "start")
    .style("fill", "black")
    .text(function(d) {
      return "x:" + d.x
    });

  svg.on("click", function() {
    var coords = d3.mouse(this);

    var newData = {
      x: coords[0],
    }
    
    data.push(newData);

    var newGroup = svg.selectAll(".group")
      .data(data, function(d){return d.x})
      .enter()
      .append("g")
      .attr("class", "group")
          .call(d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended))
    .on("click", removeElement);
      
      newGroup.append("rect")
      .attr("x", function(d) {
        return d.x;
      })
      .attr("y", 200)
      .attr("height", 100)
      .attr("width", 15)
      .style("fill", "steelblue")
      .attr('id', function(d, i) {
        return 'rect_' + i;
      })
      .attr("rx", 6)
      .attr("ry", 6)
      .attr("stroke-width", 2)
      .attr("stroke", "black");
  });


  function dragstarted(d) {
    d3.select(this).raise().classed("active", true);
  }

  function dragged(d) {
    d3.select(this).select("text")
      .attr("x", d.x = d3.event.x);
    d3.select(this).select("rect")
      .attr("x", d.x = d3.event.x);
  }

  function dragended(d) {
    d3.select(this)
      .classed("active", false);
  }

  function removeElement(d) {
    d3.event.stopPropagation();
    data = data.filter(function(e) {
      return e != d;
    });
    d3.select(this)
      .remove();
  }
</script>

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download