【问题标题】:D3.zoom jumps when using mouse wheel after programmatically zoom以编程方式缩放后使用鼠标滚轮时 D3.zoom 跳转
【发布时间】:2019-08-23 22:41:31
【问题描述】:

当我单击鼠标缩放到特定位置然后尝试平移或使用鼠标滚轮时,缩放行为会跳跃。似乎我的缩放级别正在恢复,就像单击鼠标之前一样。

这是我的事件处理程序:

function click(d) {
    var x, y, k;

    if (d && centered !== d) {
       var centroid = path.centroid(d);
       x = centroid[0];
       y = centroid[1];
       k = 4;
       centered = d;
     } else {
       x = width / 2;
       y = height / 2;
       k = 1;
       centered = null;
    }


    svgContainer.transition()
       .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")");
}

这就是我“激活”缩放和平移功能的方式。

var svgContainer = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)
    .call(d3.zoom().on("zoom", function () {
         svgContainer.attr("transform", d3.event.transform)
    }))
    .append("g")
    ...

svgContainer.selectAll(null)
    .data(feat.features)
    .enter()
    .append("path")
    .on("click", click)
     ...

【问题讨论】:

    标签: d3.js svg zooming


    【解决方案1】:

    背景

    您在 click 函数中操纵应用于元素的变换,但没有更新缩放状态以反映这一点。 d3.zoom 根本不跟踪元素的变换。因此,当您独立于 d3.zoom 修改元素的变换属性时,d3.zoom 不再“知道”该元素的变换是什么——它保持不变。 D3.zoom 确实跟踪缩放状态 - 在内部且独立于任何元素的变换属性。

    d3.zoom 不跟踪元素的变换可能看起来很奇怪,但这是有充分理由的。 d3.zoom 并不总是用于操纵元素的变换,它可能会改变元素宽度或比例等内容,而该元素的变换保持不变。这是我的bl.ock,这里的 d3.zoom 只操纵画布上的圆的半径。

    问题

    由于您没有在单击事件中更新缩放状态,因此 d3.zoom 会在应用新的缩放事件时拾取上次离开的位置,这解释了您的症状:“似乎我的缩放级别像这样恢复了是在鼠标点击之前。”

    解决方案

    那么,你需要做什么?您需要将缩放转换传递给 d3.zoom 并触发缩放事件。这样 d3.zoom 就可以获知当前的缩放状态。幸运的是,有一种方法可以做到这一点。我们采用 d3.zoomIdentity (k=1,x=0,y=0) 并根据需要进行平移和缩放,我们也可以像您所做的那样平移、缩放然后再次平移:

       // Create a zoom transform from d3.zoomIdentity
       var transform = d3.zoomIdentity
        .translate(250,150)
        .scale(k)
        .translate(-x,-y);
    

    然后我们根据文档调用 zoom.transform 来应用缩放:

    将所选元素的当前缩放变换设置为 指定的变换,瞬间发出开始、缩放和结束 事件。如果选择是一个过渡,定义一个“缩放”补间到 使用 d3.interpolateZoom 指定变换,发出开始事件 当过渡开始时,每个刻度的缩放事件 过渡,然后是过渡结束时的结束事件(或 中断)。 (link)

    我们可以调用 zoom.transform :

       // Apply the zoom and trigger a zoom event with a provided zoom transform:
       svg.call(zoom.transform, transform);
    

    因此,如果这与您所拥有的相似:

    var zoom = d3.zoom()
      .scaleExtent([1,8])
      .translateExtent([[0,0],[500,300]])
      .on("zoom",zoomed);
    
    var svg = d3.select("div")
      .append("svg")
      .attr("width",500)
      .attr("height",300)
      .call(zoom);
      
    var g = svg.append("g");
      
    var rects = g.selectAll(null)
      .data(d3.range(750))
      .enter()
      .append("rect")
      .attr("width",17)
      .attr("height",17)
      .attr("fill","#eee")
      .attr("y", function(d) { return Math.floor(d/50) * 20; })
      .attr("x", function(d) { return d%50 * 20; })
      .on("click", click);
    
    function zoomed() {
      g.attr("transform", d3.event.transform);
    }
    
    function click(d) {
       rects.attr("fill","#eee");  
       
       var clicked = d3.select(this);
       clicked.attr("fill","orange");
       
       var x = +clicked.attr("x")+10;
       var y = +clicked.attr("y")+10;
       var k = 5;
       
       var transform = "translate(" + 250 + "," + 150 + ")scale(" + k + ")translate(" + -x + "," + -y + ")";
       
       g.transition()
         .attr("transform",transform)
         .duration(1000);
    }
    rect {
      stroke-width: 1px;
      stroke: #ccc;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    <div>

    这可能与您的解决方案类似:

    var zoom = d3.zoom()
      .scaleExtent([1,8])
      .translateExtent([[0,0],[500,300]])
      .on("zoom",zoomed);
    
    var svg = d3.select("div")
      .append("svg")
      .attr("width",500)
      .attr("height",300)
      .call(zoom);
      
    var g = svg.append("g");
      
    var rects = g.selectAll(null)
      .data(d3.range(750))
      .enter()
      .append("rect")
      .attr("width",17)
      .attr("height",17)
      .attr("fill","#eee")
      .attr("y", function(d) { return Math.floor(d/50) * 20; })
      .attr("x", function(d) { return d%50 * 20; })
      .on("click", click);
    
    function zoomed() {
      g.attr("transform", d3.event.transform);
    }
    
    function click(d) {
       rects.attr("fill","#eee");  
       
       var clicked = d3.select(this);
       clicked.attr("fill","orange");
       
       var x = +clicked.attr("x")+10;
       var y = +clicked.attr("y")+10;
       var k = 5;
    
       // Create a zoom transform from d3.zoomIdentity
       var transform = d3.zoomIdentity
        .translate(250,150)
        .scale(k)
        .translate(-x,-y);
       
       // Apply the zoom and trigger a zoom event:
       svg.call(zoom.transform, transform);
    
    }
    rect {
      stroke-width: 1px;
      stroke: #ccc;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    <div>

    【讨论】:

    • 感谢您的详细解释,但它仍然跳跃。我完全按照你对我的建议做了。 jsfiddle.net/wx9r261v/1
    • 一会儿看看
    • 如果您单击缩放然后平移,是否会重置?跳跃可能只是缩放级别变化的严重程度而没有转换,如果您在缩放功能中应用转换,问题是否仍然存在:d3.select("#mainGroup").transition().attr("transform", d3.event.transform);?
    • 啊,你在两个不同的东西上调用缩放,svg 和孩子gsvgContainer 持有g,试试:d3.select("#svg-id").call(zoom.transform,transform)。这就是为什么我在上面使用两种选择,一种用于g,一种用于svg - g 只有元素所在的鼠标交互,svg 无处不在,所以我称之为缩放 svg,应用它到g。感谢您更新小提琴,这里更新了 fiddle 的更改。
    • 是的,我明白为什么这可能会令人困惑 - 大部分缩放行为(事件侦听器和状态)本质上存储在 DOM 元素本身上。但它在 DOM 元素的自定义属性中这样做(查看已调用缩放的 DOM 元素的 __zoom__on 属性)。文档和我的答案之间的区别在于,我只是将 d3-zoom 添加到 DOM 元素的属性视为缩放行为的一部分,而文档认为这些属性是分开的。我的方法确实使答案更简单,但我可以添加一些额外的说明。
    猜你喜欢
    • 1970-01-01
    • 2021-05-25
    • 2016-11-30
    • 2011-12-09
    • 2012-11-13
    • 2016-04-20
    • 1970-01-01
    • 2015-10-07
    • 1970-01-01
    相关资源
    最近更新 更多