d3 click and drag event nesting

15,830

An SVG <g> element does not have a size or area; it is a transparent container for all its descendants, and cannot intercept events for other elements (unless you are adding an event listener to be triggered during the 'capture phase').

As a parent element, events bubble up to it; likely what you are seeing is that whatever event you're using to trigger the drag start (mousedown?) on the <rect> is also bubbling up to the <g> and starting a concurrent drag of that.

To fix this, you want to stop your event from bubbling. In your event handler for the mousedown (or whatever) on the <rect> add:

function startDrag(evt){
  // whatever your code is here
  evt.stopPropagation();
}

Without your actual code (or better, a pared-down test case) it's hard to know for sure, or help you further.


Edit Here's a working version of your simple example: http://jsfiddle.net/ZrCQE/2/

Specifically, apparently d3.event is not the event itself, but an object with a sourceEvent property that references the actual event.

var dragGroup = d3.behavior.drag()
  .on('dragstart', function() {
    console.log('Start Dragging Group');
  }).on('drag', function(d, i) {
    d.x += d3.event.dx;
    d.y += d3.event.dy;
    d3.select(this).attr("transform", "translate("+d.x+","+d.y+")");
  });

var dragCircle = d3.behavior.drag()
  .on('dragstart', function(){
    d3.event.sourceEvent.stopPropagation();
    console.log('Start Dragging Circle');
  })
  .on('drag', function(d,i){
    d.cx += d3.event.dx;
    d.cy += d3.event.dy;
    d3.select(this).attr('cx', d.cx).attr('cy', d.cy)
  });

var svg = d3.select('body').append('svg').attr('viewBox','-50 -50 300 300');

var g = svg.selectAll('g').data([{x:10,y:10}])
  .enter().append('g').call(dragGroup);

g.append('rect').attr('width', 100).attr('height', 100);

g.selectAll('circle').data([{cx: 90,cy:80}])
  .enter().append('circle')
    .attr('cx', function(d){ return d.cx })
    .attr('cy', function(d){ return d.cy })
    .attr('r', 30)
    .call(dragCircle);​
Share:
15,830
ballaw
Author by

ballaw

Updated on June 09, 2022

Comments

  • ballaw
    ballaw almost 2 years

    I am nesting a lot of elements inside of a g element like so:

    <g>
        <rect></rect>
        <text></text>
        ...
    </g>
    

    However, there are some rects that I want to be able to have drag events of their own. The problem is that when you put stuff inside of a g tag, its size expands to contain those tags. So even though I can assign events there is no way they can be triggered because the g tag's event is somehow more important even though the rect is on top of it.

    Is there some sort of workaround that you guys know of?

    EDIT: Here's a simple complete case in its entirety. A rect and a circle inside a g. The g is draggable and the circle should be draggable too but is not.

    var gDragBehav = d3.behavior.drag()
        .on('drag', gDragDrag)
    
    function gDragDrag(d,i) {
        d.x += d3.event.dx;
        d.y += d3.event.dy;
        d3.select(this)
            .attr('x', d.x)
            .attr('y', d.y)
            .attr("transform", "translate(" + d.x + "," + d.y + ")");
    }
    
    var circleDragBehav = d3.behavior.drag()
        .on('drag', circleDragDrag);
    
    function circleDragDrag(d,i) {
        console.log('dragging a circle')
        d.cx += d3.event.dx;
        d.cy += d3.event.dy;
        d3.select(this)
            .attr('cx', d.cx)
            .attr('cy', d.cy)
    }
    
    var svg = d3.select('body').append('svg')
    
    var g = svg.selectAll('g').data([{x: 10, y:10}])
        .enter().append('g').call( gDragBehav )
    
    g.append( 'rect' ).attr('width', 100 ).attr('height', 100 )
    
    g.selectAll( 'circle' ).data([{cx: 0, cy:0}])
        .enter().append( 'circle' )
        .attr('cx', function(d) { return d.cx } ).attr('cy', function(d) { return d.cy } )
        .attr('r', 40 )
        .call( circleDragBehav )
    

    EDIT: Here's some of the code

    var group = this.d3svg.selectAll('g' + '.' + this.className)
      .attr('x', this.drawFuncs['x'] )
      .attr('y', this.drawFuncs['y'] )
      .attr("transform", this.drawFuncs['translate'] )
      .attr('class', this.className )
      .call(gDragBehav)
      .on( 'click', blockClickMenu )
    
    ports = ['AtomicPort']
    for ( port in ports ) {  
      drawPort.call( this, group, ports[port] )
    }
    
    function drawPort( d3svg, portName, redraw ) {
      d3svg.selectAll('rect.' + portName)
        .data( function(d) { return d.ports[ portName ] } )
        .enter().append('rect')
        .attr('x', this.drawFuncs['x'] )
        .attr('y', this.drawFuncs['y'] )
        .attr('width', this.drawFuncs['width'] )
        .attr('height', this.drawFuncs['height'] )
        .attr('class', portName )
        .call(portDragBehav)
    
      var portDragBehav = d3.behavior.drag()
        .on('drag', portDragDrag);
    
      function portDragDrag(d,i) {
        d.x += d3.event.dx;
        d.y += d3.event.dy;
        d3.select(this)
          .attr('x', d.x)
          .attr('y', d.y)
        d3.event.stopPropagation();
      }
    
      var gDragBehav = d3.behavior.drag()
        .on('dragstart', gDragStart)
    
      function gDragDrag(d,i) {
        d.x += d3.event.dx;
        d.y += d3.event.dy;
        d3.select(this)
          .attr('x', d.x)
          .attr('y', d.y)
          .attr("transform", "translate(" + d.x + "," + d.y + ")");
        d3.event.stopPropagation(); //Uncaught TypeError: Object #<Object> has no method 'stopPropagation'
      }
    
  • ballaw
    ballaw about 12 years
    I am attaching the drag behavior to the g element itself, so that everything in the g's body will move. The problem is that the elements inside of the g tag weren't being called at all. I'm not sure what you though is the problem but I do not have a handle on the event. How can I call stopPropagation?
  • Phrogz
    Phrogz about 12 years
    @ballaw That's good (but expected) information. It does not change my answer.
  • ballaw
    ballaw about 12 years
    OK, but I do not have a handle on the event. How can I call stopPropagation?
  • Phrogz
    Phrogz about 12 years
    @ballaw I cannot help you without seeing your event registrations on the <g> and <rect>.
  • Phrogz
    Phrogz about 12 years
    "The problem is that the elements inside of the g tag weren't being called at all." I do not know what this sentence means. Particularly, what do you mean by "called"?
  • ballaw
    ballaw about 12 years
    Whoops. I mean the events, not the elements. I posted some more code. There is a block that is draggable and inside of it there are smaller draggable blocks.
  • ballaw
    ballaw about 12 years
    I added stopPropagation at the end of both drag behaviors but to no avail. The problem is that the nested port drag behavior is never called, not that it's propagating. I added console.log inside the port drag and it never prints anything.
  • Phrogz
    Phrogz about 12 years
    @ballaw Sorry, I've reached my limit on this with only tiny glimpses into your code. Create a simple, pared-down, reproducible test case with minimal data and code and I might renew my efforts.
  • ballaw
    ballaw about 12 years
    Wait, d3.event can't call stopPropagation. I get an error. I guess it is a different kind of event.
  • Phrogz
    Phrogz about 12 years
    @ballaw Winner! That's what was needed. See my edit and working example. :)
  • Andy
    Andy almost 11 years
    d3.event.sourceEvent.stopPropagation(); needs to go on 'dragStart' not 'drag'