让我们考虑一个使用 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>
再次 - 仅对圆圈进行缩放交互,拖动/平移一个圆圈以与另一个圆圈进行交互