为什么此代码不重置 d3 缩放状态?

Why does this code not reset d3 zoom state?

以下代码成功地允许我的 canvas 被平移和缩放。还有一个按钮可以重置为原始状态。但是,在按下按钮 后我继续平移或缩放 之后,它会恢复为按下按钮之前存在的 translate/scale。

这是在 d3 v7 中,我使用 d3.behavior.zoom 看到了 v3 的答案,但我相信那不再是 属性。似乎在“缩放”事件中存储了一些内部状态——有没有办法将其设置为 (x:0,y:0,k:1)?我的代码有缺陷吗?

<script src="d3.js"></script>
<script>

var svg_orig = d3.select("svg"),
    width = +svg_orig.attr("width"),
    height = +svg_orig.attr("height");

var svg = svg_orig.append("g");

var zoom = d3.zoom()
              .scaleExtent([.5,5])
        
svg_orig.call(zoom.on("zoom", function (event) {
              svg.attr("transform", function(e){return `translate(${event.transform.x},${event.transform.y}) scale(${event.transform.k})`});
         }))

...

// reset button code attempts
function reset(){
  // svg.attr("transform", d3.zoomIdentity);
  // svg.call(zoom.transform,d3.zoomIdentity.translate(0, 0).scale(1));
  svg.transition().duration(500).attr("transform", d3.zoomIdentity);
}

让我们考虑一个使用 d3-drag 的类比。拖动本质上是仅具有固定 scale/pan 的缩放(考虑到与 d3-zoom 的重叠,我似乎记得关于完全删除 d3-drag 的讨论)。

举个例子:

const svg = d3.select("svg");
const datum = {x: 100, y:100}
const drag = d3.drag()
  .on("drag", function(event,d) {
    d3.select(this)
      .attr("cx",d.x = event.x)
      .attr("cy",d.y = event.y);
  })

svg.append("circle")
  .datum(datum)
  .attr("cx",d=>d.x)
  .attr("cy",d=>d.y)
  .attr("r", 10)
  .call(drag);
circle { cursor: pointer; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
<svg width=500 height=300></svg>

拖动将与本质上存储“拖动状态”的数据相关。数据和元素虽然绑定,但仍然是分开的:我们必须在数据更新后显式更新元素的视觉表示。

现在让我们在不更新拖动状态的情况下移动圆圈:

const svg = d3.select("svg");
const datum = {x: 100, y:100}
const drag = d3.drag()
  .on("drag", function(event,d) {
    d3.select(this)
      .attr("cx",d.x = event.x)
      .attr("cy",d.y = event.y);
  })

svg.append("circle")
  .datum(datum)
  .attr("cx",d=>d.x)
  .attr("cy",d=>d.y)
  .attr("r", 10)
  .call(drag);
 
d3.select("button").on("click", function() {
     d3.select("circle")
       .attr("cx", 300)
       .attr("cy", 50)
  })
circle { cursor: pointer; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
<button>Move</button>
<svg width=500 height=300></svg>

如果我们用按钮移动圆,我们会在不更新基准的情况下重新定位圆:这会导致拖动时发生跳转:拖动是相对于基准,但特征不再根据基准定位.

上面的代码片段在不更新拖动状态的情况下定位圆圈,与您使用时的情况非常相似:

  svg.transition().duration(500).attr("transform", d3.zoomIdentity);

您正在更改变换而不更新缩放状态。

两者之间唯一真正的区别是缩放状态不存储在数据上——它存储在元素的其他地方。使用 d3-drag 很容易修复的问题:定位圆时只需更新基准:

const svg = d3.select("svg");
const datum = {x: 100, y:100}
const drag = d3.drag()
  .on("drag", function(event,d) {
    d3.select(this)
      .attr("cx",d.x = event.x)
      .attr("cy",d.y = event.y);
  })

svg.append("circle")
  .datum(datum)
  .attr("cx",d=>d.x)
  .attr("cy",d=>d.y)
  .attr("r", 10)
  .call(drag);
 
d3.select("button").on("click", function() {
     d3.select("circle")
       .attr("cx", d=>d.x=300)
       .attr("cy", d=>d.y=50)
  })
circle { cursor: pointer; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
<button>Move</button>
<svg width=500 height=300></svg>

使用 d3-zoom 时,缩放状态不那么容易访问。缩放行为在缩放事件期间更新缩放状态,因此我们需要触发缩放事件来更新缩放状态。由于缩放事件通常用于更新元素的视觉表示,因此我们还确保缩放状态和视觉表示之间的一致性。

现在你几乎已经在被注释掉的代码中找到了正确的方法:

 svg.attr("transform", d3.zoomIdentity);

除非您需要在最初调用缩放行为的同一元素上触发缩放事件。像拖动一样,可以在多个元素上调用缩放行为:它只是一种行为,本身不跟踪任何状态:

const svg = d3.select("svg");
const data = [{x: 100, y:100},{x:200,y:100}]
const zoom = d3.zoom()
  .on("zoom", function(event,d) {
    d3.select(this).select("circle")
      .attr("transform",event.transform)
  })

svg.selectAll(null)
  .data(data)
  .enter()
  .append("g")
  .call(zoom)
  .append("circle")
  .attr("cx",d=>d.x)
  .attr("cy",d=>d.y)
  .attr("r", 10)
  
 
circle { cursor: pointer; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
<svg width=500 height=300></svg>

上面的代码片段两次使用相同的缩放行为,每个圆圈一次(缩放交互仅与圆圈本身有关,而不是空 space)。理想情况下,这表明该行为与缩放状态无关。如上所述,缩放状态存储在元素本身,如果行为跟踪缩放状态,我们不能在多个元素上使用它。

所以如果我们使用:

 svg.call(zoom)

其次是:

 svg2.call(zoom.transform,someTransform)

当我们在 svg2 上应用缩放变换时,存储在 svg 上的缩放状态不会是 affected/updated - 当我们在 [=29= 上恢复缩放时会导致跳跃].

使用 zoom.transform 结果一开始看起来不错的原因是缩放事件更新了此处目标元素的视觉表示(你所谓的缩放和你事件处理程序中的修改当然是独立的):

const svg = d3.select("svg");
const data = [{x: 100, y:100},{x:200,y:100}]
const zoom = d3.zoom()
  .on("zoom", function(event,d) {
    d3.select(d.target).select("circle")
      .attr("fill", d3.scaleLinear()
          .range(["orange","lightgreen","steelblue"])
          .domain([-250,0,250])(event.transform.x))
      .attr("r",d3.scaleLinear()
         .range([50,10,50])
         .domain([-125,0,125])(event.transform.y))
  })

svg.selectAll(null)
  .data(data)
  .enter()
  .append("g")
  .call(zoom)
  .append("circle")
  .attr("cx",d=>d.x)
  .attr("cy",d=>d.y)
  .attr("r", 20)
  .each(function(d,i,nodes) {
    d.target = nodes[+!i].parentNode;
  })
circle { cursor: pointer; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
<svg width=500 height=300></svg>

同样 - 仅在圆上缩放交互,drag/pan 一个圆与另一个圆交互