I'm implementing the drag example from https://observablehq.com/@d3/circle-dragging-i using d3v7.
When I try the code it works correctly for the first drag, however from the start of the second drag, the position resets/jumps to the original one provided in the join data.
It appears that the event position is influenced by the data. I have confirmed this behavior using logs that tell me the starting position and the event and comparing it to the one given to me by the browser inspector.
After much toiling I found out that the issue here is in the way that the data has it's variables called x
and y
:
var circles = d3.range(20).map(i => ({
x: Math.random() * (width - radius * 2) radius,
y: Math.random() * (height - radius * 2) radius,
}));
By changing the data:
var circles = d3.range(20).map(i => ({
posx: Math.random() * (width - radius * 2) radius,
posy: Math.random() * (height - radius * 2) radius,
}));
I am able to drag the circles as intended.
Interestingly, if I update the position of the variable circles, this updated data is not reflected in the jump behaviour, by that I mean that the jump will happen to the initial value.
Should the name of the data influence the initial position of the items?
Why does the drag position resets after the first drag? (I tried changing the x
, y
properties alongside cx
and cy
, but the reset still happens).
Full code with problematic behaviour:
<!DOCTYPE html>
<html lang="en">
<body>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
radius = 32;
height = 600;
width = 800;
function dragStart(event,d){
d3.select(this)
.style("stroke", "black")
console.log("Start",[event.x,event.y])
var current = d3.select(this);
console.log([current.attr("cx"),current.attr("cy")]);
console.log(current.attr("translate"));
}
function dragging(event,d){
var current = d3.select(this);
current
.attr('cx', event.x)
.attr('cy', event.y);
}
function dragEnd(event,d){
d3.select(this)
.style("stroke", "")
console.log("End",[event.x,event.y])
var current = d3.select(this);
console.log(d3.select(this));
}
drag = d3.drag()
.on("start", dragStart)
.on("drag", dragging)
.on("end", dragEnd);
var svg = d3.select("body")
.append("svg")
.attr("viewBox", [0, 0, width, height])
.attr("stroke-width", 2);
var circles = d3.range(20).map(i => ({
x: Math.random() * (width - radius * 2) radius,
y: Math.random() * (height - radius * 2) radius,
}));
console.log(circles);
console.log(drag);
svg.selectAll("circle")
.data(circles)
.join("circle")
.attr("cx", (d)=>d.x)
.attr("cy", (d)=>d.y)
.attr("r", radius)
.attr("fill", (d, i) => d3.schemeCategory10[i % 10])
.on("click",e => console.log(d3.pointer(e)))
.call(drag);
</script>
</body>
</html>
CodePudding user response:
Your issue is not in how you set-up circles
but in how you have implemented dragging
.
In the Observable, the equivalent dragged
function is:
function dragged(event, d) {
d3.select(this).attr("cx", d.x = event.x).attr("cy", d.y = event.y);
}
Note that in thh attr
s that cx
/ d.x
and cy
/ d.y
are updated per the event
values which changes the underlying data. See here.
But, in your implementation:
function dragging(event,d){
var current = d3.select(this);
current
.attr("cx", event.x)
.attr("cy", event.y);
}
You are not updating the underlying values, you just animate the movement by updating cx
and cy
. So on the next drag event the circle 'jumps' back to its original position and you get the disconnect between the pointer and the circle.
See the working example below with one circle - the log
is now on circles
rather than the selection and you can see the underlying data be updated. If you revert to your original dragging
definition and keep the log
for circles
you will see the circle move, but the underlying data unchanged.
<!DOCTYPE html>
<html lang="en">
<body>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
radius = 32;
height = 200; // change to suit Stack Overflow snippet
width = 500; // change to suit Stack Overflow snippet
function dragStart(event,d){
d3.select(this)
.style("stroke", "black")
//console.log("Start",[event.x,event.y])
//var current = d3.select(this);
//console.log([current.attr("cx"),current.attr("cy")]);
//console.log(current.attr("translate"));
}
function dragging(event,d){
var current = d3.select(this);
current
.attr("cx", d.x = event.x)
.attr("cy", d.y = event.y);
console.log(circles)
}
function dragEnd(event,d){
d3.select(this)
.style("stroke", "")
//console.log("End",[event.x,event.y])
//var current = d3.select(this);
//console.log(d3.select(this));
}
drag = d3.drag()
.on("start", dragStart)
.on("drag", dragging)
.on("end", dragEnd);
var svg = d3.select("body")
.append("svg")
//.attr("viewBox", [0, 0, width, height])
.attr("width", width) // change to suit Stack Overflow snippet
.attr("height", height)
.attr("stroke-width", 2);
var circles = d3.range(1).map(i => ({
x: Math.random() * (width - radius * 2) radius,
y: Math.random() * (height - radius * 2) radius,
}));
//console.log(circles);
//console.log(drag);
svg.selectAll("circle")
.data(circles)
.join("circle")
.attr("cx", (d)=>d.x)
.attr("cy", (d)=>d.y)
.attr("r", radius)
.attr("fill", (d, i) => d3.schemeCategory10[i % 10])
//.on("click",e => console.log(d3.pointer(e)))
.call(drag);
</script>
</body>
</html>