【问题标题】:Why does this code not reset d3 zoom state?为什么这段代码不重置 d3 缩放状态?
【发布时间】:2021-09-24 10:32:51
【问题描述】:

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

这是在 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);
}

【问题讨论】:

  • 看起来像this 问题。但我看到你已经注释掉了类似的代码。 svg.call(zoom.transform,d3.zoomIdentity) 在重置功能中对你不起作用?
  • @AndrewReid 对我不起作用。当我单击按钮以重置()时,它将居中并缩放到原始位置。但是当我拖动或缩放时,它将从重置前的状态开始。所以如果我放大 5 倍,然后单击重置它将转到 1 倍。但如果我然后拖动画布,它会回到 5 倍。
  • 抱歉,我的错,没有仔细阅读:您需要在最初调用缩放的同一元素上调用转换:svg_origsvg_orig.call(zoom.transform,d3.zoomIdentity)。 (d3-zoom 将缩放状态存储在它被调用的元素上)。
  • 那行得通。您是否可以将此作为答案提交并让我了解发生的幕后情况?为什么调用svg_orig的时候能用,用svg跳转到另一个状态?
  • 会做,但今天稍晚一点。

标签: javascript svg d3.js


【解决方案1】:

让我们考虑一个使用 d3-drag 的类比。拖动本质上是仅具有固定比例/平移的缩放(鉴于与 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>

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

上面的sn-p定位圆而不更新拖拽状态,和你使用的时候差不多:

  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>

上述 sn-p 两次使用相同的缩放行为,每个圆圈一次(缩放交互仅与圆圈本身,而不是空白空间)。理想情况下,这表明行为独立于缩放状态。如上所述,缩放状态存储在元素本身上,如果行为跟踪缩放状态,我们不能在多个元素上使用它。

所以如果我们使用:

 svg.call(zoom)

接着是:

 svg2.call(zoom.transform,someTransform)

当我们在 svg2 上应用缩放变换时,存储在 svg 上的缩放状态不会受到影响/更新 - 当我们在 svg 上恢复缩放时会导致跳转。

使用 zoom.transform 时结果一开始看起来不错的原因是因为 zoom 事件更新了此处目标元素的视觉表示(您所说的 zoom on 以及您在事件处理程序中修改的内容是当然是独立的):

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>

再次 - 仅对圆圈进行缩放交互,拖动/平移一个圆圈以与另一个圆圈进行交互

【讨论】:

    猜你喜欢
    • 2018-01-16
    • 1970-01-01
    • 2015-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-27
    • 1970-01-01
    相关资源
    最近更新 更多