1

I am trying to draw a UI in d3js where I have a top panel and I should be able drag and drop elements to a group and copy them. I have achieved that goal, but there is a bug in my code. What I actually want to do is, drag the circle and copy it. But when I click the circle in the top panel it automatically triggers the drag event and copy it self. How can I stop this behaviour?

<svg height="600" width="600" style="background: black">

    <g>
        <rect x="0" y="0" , width="600" height="40" style="fill:gold;"></rect>
        <circle id='drag' cx="15" cy="20" init-cx="15" init-cy="20" r="10"
                style="stroke: white; stroke-width: 2px; fill:blue"/>

    </g>

    <g id="playground">
        <g>
            <circle id='top' cx="180" cy="120" r="30" style="stroke: white; stroke-width: 2px; fill:white"/>
        </g>
        <g>
            <circle id='top' cx="200" cy="220" r="30" style="stroke: white; stroke-width: 2px; fill:white"/>
        </g>
        <g>
            <circle id='top' cx="320" cy="150" r="50" style="stroke: white; stroke-width: 2px; fill:white"/>
        </g>
    </g>
</svg>

<script>

    $(document).ready(function () {


        var move = d3.behavior.drag()
            .on('drag', function () {

                console.log('dragging');

                var curr = d3.select(this)
                    .attr({
                        cx: d3.mouse(this)[0],
                        cy: d3.mouse(this)[1]
                    })


            })
            .on('dragend', function () {

                var curr = d3.select(this);

                d3.select('#playground')
                    .append('circle')
                    .attr({
                        cx : curr.attr('cx'),
                        cy : curr.attr('cy'),
                        r : curr.attr('r')
                    })
                    .style({
                        fill : 'white',
                        stroke : 'red',
                        'stroke-width' : '2px'
                    })
                ;

                curr.attr({
                    cx : curr.attr('init-cx'),
                    cy : curr.attr('init-cx')
                });
            })
            ;


        d3.select('#drag').call(move);


    });


</script>

here is the fiddle of my work. https://jsfiddle.net/fawzan/my2g724L/

Fawzan
  • 4,738
  • 8
  • 41
  • 85

5 Answers5

1

You just need to check the target of drag-end event is the same circle and copy the circle only if they does not match.

.on('dragend', function() {
    if (d3.event.sourceEvent.target != this) {
      var curr = d3.select(this);

      d3.select('#playground')
        .append('circle')
        .attr({
          cx: curr.attr('cx'),
          cy: curr.attr('cy'),
          r: curr.attr('r')
        })
        .style({
          fill: 'white',
          stroke: 'red',
          'stroke-width': '2px'
        });

      curr.attr({
        cx: curr.attr('init-cx'),
        cy: curr.attr('init-cx')
      });
    }
});

var move = d3.behavior.drag()
  .on('drag', function() {

    console.log('dragging');
    console.log(this)
    console.log(this)

    var curr = d3.select(this)
      .attr({
        cx: d3.mouse(this)[0],
        cy: d3.mouse(this)[1]
      })


  })
  .on('dragend', function() {
    if (d3.event.sourceEvent.target != this) {
      var curr = d3.select(this);

      d3.select('#playground')
        .append('circle')
        .attr({
          cx: curr.attr('cx'),
          cy: curr.attr('cy'),
          r: curr.attr('r')
        })
        .style({
          fill: 'white',
          stroke: 'red',
          'stroke-width': '2px'
        });

      curr.attr({
        cx: curr.attr('init-cx'),
        cy: curr.attr('init-cx')
      });
    }
  });


d3.select('#drag').call(move);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg height="600" width="600" style="background: black">

  <g>
    <rect x="0" y="0" , width="600" height="40" style="fill:gold;"></rect>
    <circle id='drag' cx="15" cy="20" init-cx="15" init-cy="20" r="10" style="stroke: white; stroke-width: 2px; fill:blue" />

  </g>

  <g id="playground">
    <g>
      <circle id='top' cx="180" cy="120" r="30" style="stroke: white; stroke-width: 2px; fill:white" />
    </g>
    <g>
      <circle id='top' cx="200" cy="220" r="30" style="stroke: white; stroke-width: 2px; fill:white" />
    </g>
    <g>
      <circle id='top' cx="320" cy="150" r="50" style="stroke: white; stroke-width: 2px; fill:white" />
    </g>
  </g>
</svg>
Gilsha
  • 14,431
  • 3
  • 32
  • 47
1

Hope this will be useful for you, Have a look. I've added little code in 'dragend', that is here I'm deciding whether to create/append a circle to playground or not, by using circle's attributes like init-cx and init-cy. The code I've added is

var initX = (curr.attr('init-cx')*1);
var currX = (curr.attr('cx')*1);
var initY = (curr.attr('init-cy')*1);
var currY = (curr.attr('cy')*1);                  
if(((currX) > (initX+20)) || ((currY) > (initY+20))){
//Here code to append a circle to playground
}

Fiddle

:D

saikiran.vsk
  • 1,788
  • 1
  • 10
  • 11
  • Yep, this makes a lot of sense. actually I just had to use the currX, initY point and compare them :D – Fawzan Nov 25 '15 at 09:44
0

see your curr.attr('cx') offset is returning 15 when clicked.So you can prevent the default action by adding condition on dragend method:

if(curr.attr('cx')==15){
   return false;
}

More generic:

var isClicked = false;
var move = d3.behavior.drag()
    .on('dragstart', function() {
        isClicked = true;
    })
    .on('drag', function() {
        isClicked = false;

        var curr = d3.select(this)
            .attr({
                cx: d3.mouse(this)[0],
                cy: d3.mouse(this)[1]
            })
    })
    .on('dragend', function() {
        var curr = d3.select(this);
        if (!isClicked) {
            d3.select('#playground')
                .append('circle')
                .attr({
                    cx: curr.attr('cx'),
                    cy: curr.attr('cy'),
                    r: curr.attr('r')
                })
                .style({
                    fill: 'white',
                    stroke: 'red',
                    'stroke-width': '2px'
                });
        }

        curr.attr({
            cx: curr.attr('init-cx'),
            cy: curr.attr('init-cx')
        });
    });


d3.select('#drag').call(move);
d3.select('#drag').on('click', click);
function click(d) {
    isClicked = true;
}

check out Fiddle:

More Info

Community
  • 1
  • 1
Afsar
  • 3,104
  • 2
  • 25
  • 35
  • 1
    No offence, but it looks like a lame solution to me. I do not prefer hard coding the coordinates in the program. Because the actual UI is much complicated. – Fawzan Nov 25 '15 at 05:24
  • My bad , you can set a flag and detect `click ` or `drag` event , based on those you can perform action – Afsar Nov 25 '15 at 05:46
0

Use boolean to monitor whether the drag is initiated or not. In this way you can stop dragend function.

$(document).ready(function () {

    var isDragged = false;
    var move = d3.behavior.drag()
        .on('drag', function () {

            console.log('dragging');
            isDragged = true;
            var curr = d3.select(this)
                .attr({
                    cx: d3.mouse(this)[0],
                    cy: d3.mouse(this)[1]
                })


        })
        .on('dragend', function () {
            if (!isDragged) return;
            isDragged = false;
            var curr = d3.select(this);
            d3.select('#playground')
                .append('circle')
                .attr({
                    cx : curr.attr('cx'),
                    cy : curr.attr('cy'),
                    r : curr.attr('r')
                })
                .style({
                    fill : 'white',
                    stroke : 'red',
                    'stroke-width' : '2px'
                })
            ;

            curr.attr({
                cx : curr.attr('init-cx'),
                cy : curr.attr('init-cx')
            });
        })
        ;


    d3.select('#drag').call(move);


});
Sathya
  • 233
  • 1
  • 4
  • 13
0

For D3 v4 (might be applicable to v5 and v6), you can determine the position of the node during the dragged event and see if that differs from its original position. Assuming it doesn't, alphaTarget is never called.

For example:

function dragstarted(d) {
    // if (!d3.event.active) simulation.alphaTarget(0.2).restart();
    d.fx = d.x;
    d.fy = d.y;
}

function dragged(d) {
    if (d3.event.active && (d.fy != d3.event.y || d.fx != d3.event.x)) {
        simulation.alphaTarget(0.5).restart();
    }

    d.fx = d3.event.x;
    d.fy = d3.event.y;
}


function dragended(d) {
    if (!d3.event.active) simulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
}
user0000001
  • 2,092
  • 2
  • 20
  • 48