简体   繁体   中英

Attaching drag behaviour without using data().enter() in D3 v4.0.0-alpha.40

I have a project with a draggable circle. In the process of upgrading from D3 v3 > v4.0.0-alpha.40 the drag behaviour broke. It only seems to work when the draggable element is created using data([1]).enter().append('circle') . See the simple jsfiddle example for a simplified version

Can someone explain what I'm doing wrong?

Edit 1 : Github issue opened

Edit 2 : Working jsfiddle as per accepted answer

[This is copied from my response to your issue .] This is the expected behavior, though perhaps it could be improved. As the documentation for drag .subject says:

The default subject is the datum of the element in the originating selection (see drag ) that received the initiating input event. When dragging circle elements in SVG, the default subject is the datum of the circle being dragged…

And:

If the subject is null or undefined, no drag gesture is started for this pointer; however, other starting touches may yet start drag gestures.

In your failing example, the datum of your circle is undefined, so it can't initiate a drag gesture. Giving the circle a non-null datum, such as 1, fixes it.

Yet even your non-failing example isn't ideal because the drag gesture can't infer the x - and y -coordinates of the circle, and so when you start a drag gesture, the circle center will jump to the mouse (or touch) position, rather than maintaining the relative position of the circle and pointer. A better way of creating a single, draggable circle would be:

d3.select("svg").append("circle")
    .datum({x: 4 * 30, y: 4* 30})
    .attr("r", 30)
    .attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; });

It's possible we should change the default subject to be something else if the element's datum is undefined… such as the element itself. That would make the drag behavior work as you expect, although the above code is a better solution in my opinion.

When first tackling this problem I encountered two problems, which thwarted my plans of attack. But as the development of v4 progresses Mike Bostock has cleared the way:

  1. The first problem was the missing fallback to the element itself if there is no data present (removed by commit 8b25a53 ). My originial answer (see below) solved this by explicitely setting the element to be the subject of the drag. This was addressed by issue #18 and was reverted by commit 4e4855f , thus eliminating the need to set the subject.

  2. Looking at it again, I realized that the properties dx and dy got removed from the DragEvent . I opened issue #21 asking Mike if we could have them back. Fortunately, he agreed and brought them back with commit 24e9583 .

The missing links are now available in the latest build for version v4.0.0-alpha.44.

That said, I was ready to implement the no data approach I thought of in the first place:

 d3.selectAll("circle") .call(d3.drag() .on("drag", function() { this.transform.baseVal .appendItem(this.ownerSVGElement.createSVGTransform()) .setTranslate(d3.event.dx, d3.event.dy); this.transform.baseVal.consolidate(); }) ); 
 <script src="https://d3js.org/d3.v4.0.0-alpha.44.js"></script> <svg width="300" height="300"> <circle cx="50" cy="50" r="10" fill="blue"/> <circle cx="100" cy="100" r="10" fill="red"/> </svg> 

The solution uses standard SVG transformations to translate the elements which got the drag behavior attached to them. It creates a new SVGTransform , appends it to the elements SVGTransformList , sets the translation's values to the DragEvent s relative dx and dy coordinates and consolidates the transformation list for the sake of brevity.

This even circumvents the the jumps Mike Bostock mentioned in the second part of his answer , which are caused since the position of the element relative to the mouse pointer could not be inferred.


Obsolete: As the development of v4.0 evolves, the original answer is no longer valid. It was meant for versions 4.0.0-alpha.40 and below.

As Mike Bostock mentioned the handling of the drag's subject was changed by commit 8b25a53 . The commit's comment states:

Simpler default subject.

Don't fallback to the originating element if the datum is undefined. If you want to use the originating element as the subject, make it explicit.

And, of course, you are free to " make it explicit " to get a solution for your problem. Because the drag's subject no longer defaults to the element if there is no data bound to it, you need to explicitely set the subject yourself by calling drag.subject() . If you want it to always refer to the element rather than to the data you could simply use a callback returning this .

 
 
 
 
  
  
  d3 .select("svg") .selectAll("circle") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended) .subject(function(d) { return this; })) // Explicitely set the subject to the element
 
 
  

Or, if you want it to default to any data bound and fallback to the element if no such data is available, you would be using:

 
 
 
 
  
  
  d3 .select("svg") .selectAll("circle") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended) .subject(function(d) { return d == null ? this : d; }))
 
 
  

This is actually the same logic as it was before it got removed. Have a look at updated JSFiddle for a working example.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM