Accepted answer

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) {"cx", d.x = event.x).attr("cy", d.y = event.y);

Note that in thh attrs 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 =;
    .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"> 

    <script src=""></script>
        radius = 32;
        height = 200; // change to suit Stack Overflow snippet
        width = 500; // change to suit Stack Overflow snippet
        function dragStart(event,d){
              .style("stroke", "black")
              //var current =;

        function dragging(event,d){
            var current =;
              .attr("cx", d.x = event.x)
              .attr("cy", d.y = event.y);
          function dragEnd(event,d){            
              .style("stroke", "")
            //var current =;
        drag = d3.drag()
                  .on("start", dragStart)
                  .on("drag", dragging)
                  .on("end", dragEnd);
        var svg ="body")
          //.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,
              .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)))


Related Query

More Query from same tag