在这个 d3.drag 示例中,什么保留了局部偏移量?

What's preserving local offset in this d3.drag example?

this d3 example 中拖动任何圆圈时,是什么阻止了圆圈的中心与鼠标对齐?

换句话说:当您通过单击 靠近圆圈外边缘的某处 启动圆圈拖动时,代码中的内容保留了偏移量(相对于圆圈的中心)那是在拖动开始时暗示的吗?

我看到这些 .attr() 电话:

.attr("cx", d.x = d3.event.x)
.attr("cy", d.y = d3.event.y)

但我希望 d3.event.x(和 .y)是鼠标的坐标——不考虑偏移量——因此我认为圆的中心会(错误地,从UX 观点)最终在鼠标下方。

我相信 d3 拖动主题方法会发生这种情况:

If subject is specified, sets the subject accessor to the specified object or function and returns the drag behavior. If subject is not specified, returns the current subject accessor, which defaults to:

function subject(d) { return d == null ? {x: d3.event.x, y: d3.event.y} : d; }

The subject of a drag gesture represents the thing being dragged. It is computed when an initiating input event is received, such as a mousedown or touchstart, immediately before the drag gesture starts. The subject is then exposed as event.subject on subsequent drag events for this gesture. (link)

我们可以看到,如果我们不提供主题功能,也不提供具有 x 和 y 属性的数据,那么拖动事件将导致圆圈的 centering/snapping 拖动起点:

var svg = d3.select("body")
  .append("svg")
  .attr("width",500)
  .attr("height",300);
  
var datum = {x:250,y:150}
  
var g = svg.append("g")
    
  g.append("rect")
  .attr("width",500)
  .attr("height",300)
  .attr("fill","#ddd");
  
  g.append("circle")
  .datum(datum)
  .attr("cx",function(d) { return d.x; })
  .attr("cy",function(d) { return d.y; })
  .attr("r",10);
    
g.call(d3.drag().on("drag", dragged))
  
function dragged(d) {
  d3.select(this)
    .select("circle")
    .attr("cx", d3.event.x)
    .attr("cy", d3.event.y);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

以相同的示例为例,移动将数据分配给父元素 g 允许拖动访问主题的 x 和 y 属性(在上面的示例中不存在)。这里拖动是相对于初始数据(保持不变),节点将 re-centered 使用数据中指定的初始 x 和 y 属性作为每次拖动的起点(拖动多次以查看):

var svg = d3.select("body")
  .append("svg")
  .attr("width",500)
  .attr("height",300);
  
var datum = {x:250,y:150}
  
var g = svg.append("g")
  .datum(datum);
  
  g.append("rect")
  .attr("width",500)
  .attr("height",300)
  .attr("fill","#ddd");
  
  g.append("circle")
  .attr("cx",function(d) { return d.x; })
  .attr("cy",function(d) { return d.y; })
  .attr("r",10);
    
g.call(d3.drag().on("drag", dragged))
  
function dragged(d) {
  d3.select(this)
    .select("circle")
    .attr("cx", d3.event.x)
    .attr("cy", d3.event.y);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

然后我们可以更新主体的数据,这使得每个拖动事件相对于圆圈的当前位置而不是初始位置:

var svg = d3.select("body")
  .append("svg")
  .attr("width",500)
  .attr("height",300);
  
var datum = {x:250,y:150}
  
var g = svg.append("g")
  .datum(datum);
  
  g.append("rect")
  .attr("width",500)
  .attr("height",300)
  .attr("fill","#ddd");
  
  g.append("circle")
  .attr("cx",function(d) { return d.x; })
  .attr("cy",function(d) { return d.y; })
  .attr("r",10);
    
g.call(d3.drag().on("drag", dragged))
  
function dragged(d) {
  d3.select(this)
    .select("circle")
    .attr("cx", d.x = d3.event.x)
    .attr("cy", d.y = d3.event.y);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

深入研究拖动代码,我们可以看到,当拖动开始时,如果没有向主题方法提供任何功能,则拖动 x,y 开始和主题 x,y 之间的区别是计算:

  dx = s.x - p[0] || 0;
  dy = s.y - p[1] || 0;

其中 p 是鼠标的起始位置。 s 是主语。

这解释了为什么当没有提供 x 或 y 属性时,圆会捕捉到拖动开始的位置。在计算输出时,d3设置x和y值为:

p[0] + dx,
p[1] + dy

其中 p 是当前鼠标位置。

所以 d3.event.x/.y 不应该是鼠标的绝对位置,而是圆圈的绝对位置,因为拖动指定的位置发生了相对变化。通过主题,鼠标位置的相对变化被转换为被拖动项目的绝对位置。

这是一个自定义主题的示例,其中拖动将相对于 [100,100],并且圆圈将在每个拖动事件开始时捕捉到那里:

var svg = d3.select("body")
  .append("svg")
  .attr("width",500)
  .attr("height",300);
  
var datum = {x:250,y:150}
  
var g = svg.append("g")
  .datum(datum);
  
  g.append("rect")
  .attr("width",500)
  .attr("height",300)
  .attr("fill","#ddd");
  
  g.append("circle")
  .attr("cx",function(d) { return d.x; })
  .attr("cy",function(d) { return d.y; })
  .attr("r",10);
    
g.call(d3.drag()
   .on("drag", dragged)
   .subject({x:100,y:100})
   )
  
function dragged(d) {
  d3.select(this)
    .select("circle")
    .attr("cx", d3.event.x)
    .attr("cy", d3.event.y);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>