【问题标题】:reversing order of d3.zoom scale and translate颠倒 d3.zoom 缩放和平移的顺序
【发布时间】:2018-12-03 12:27:31
【问题描述】:

如果你点击本例中的红色按钮:

https://bl.ocks.org/interwebjill/fe782e6f195b17f6fe6798a24c390d90

您可以看到图表平移,使圆圈位于中心,然后放大到指定的级别(再次单击按钮会缩小)。以这种方式翻译然后放大会在左侧留下一个我宁愿没有的空白。如何更改代码以使图表先缩放,然后转换为中心,以便图表中没有这种间隙?

我尝试在缩放定义和 zoomToExtent 函数中颠倒缩放顺序并进行转换,但效果没有不同。

【问题讨论】:

    标签: d3.js zooming translate


    【解决方案1】:

    问题的最终根源是 d3.interpolateZoom。这个插值器的比例插值比平移快——尽管它们大多同时转换。使用d3.interpolateZoom 实现的模式基于此paper

    因为在d3.interpolateZoom 中,缩放和平移两者的插值方式不同,所以随着缩放比平移值减小/增加的速度更快,您会在图表的一侧看到一个间隙。

    d3.interpolateZoom 用于在转换时调用缩放。

    但是,如果您使用 .attr() 直接在转换上应用转换,则 d3 转换将使用 d3.interpolateString,它将在开始和结束字符串中搜索相应的数字并在其上使用 d3.interpolateNumber。这将对缩放和平移应用相同的插值。

    使用这两种方法,我们可以比较 d3.interpolateZoom 和 d3.interpolateString 之间的差异。黑色矩形下方使用d3.interpolateString,而橙色矩形使用d3.interpolateZoom。点击一个矩形开始过渡:

    var svg = d3.select("body").append("svg")
       .attr("width", 500)
       .attr("height", 300);
       
    var g1 = svg.append("g"), g2 = svg.append("g");
    
    var zoom1 = d3.zoom().on("zoom", function() { 
       g1.attr("transform", d3.event.transform);
    });
    
    var zoom2 = d3.zoom().on("zoom", function() {
       g2.attr("transform", d3.event.transform);
    });
    
    g1.call(zoom1.transform, d3.zoomIdentity  
           .translate(150, 100)
           .scale(2));
           
    g2.call(zoom2.transform, d3.zoomIdentity
           .translate(150,100)
           .scale(2));
    
    g1.append("rect")
       .attr("x", 20)
       .attr("y", 20)
       .attr("width", 50)
       .attr("height", 50);
       
    g2.append("rect")
      .attr("x", 22)
      .attr("y", 22)
      .attr("width", 46)
      .attr("height",46)
      .attr("fill","orange");
       
    d3.selectAll("rect").on("click", function() {
                                                                   
       g1.transition()
          .duration(6000)
          .attr("transform", d3.zoomIdentity)
          .on("end", function() {
    				d3.select(this).call(zoom1.transform, d3.zoomIdentity);			  
    			})
          
       g2.transition()
          .duration(6000)
          .call(zoom2.transform, d3.zoomIdentity)
          
    
    });
    <script type="text/javascript" src="https://d3js.org/d3.v5.js"></script>

    在第一个矩形使用 .attr() 转换变换的地方,我们需要在之后调用缩放以确保缩放具有当前变换,在此示例中我们不需要,但如果你想在您需要执行此操作的变换后使用缩放

    比较这两个我们得到:

    (Y轴表示从开始属性到结束属性的过渡剩余百分比)


    您希望缩放和平移在过渡时以相同的速率同时移动。如果我们使用补间函数,我们可以做到这一点。与上面不同,我们不能只使用transition().attr("transform",newTransfrom),因为您还在绘制画布并更新轴。因此,我们需要创建自己的补间函数,该函数可以使用当前的变换和缩放,将其应用于轴、画布和标记。

    例如,而不是调用缩放(将使用 d3.interpolateZoom):

    function zoomToExtent(d0, d1) {
      zoomRect.call(zoom).transition()
        .duration(1500)
        .call(zoom.transform, d3.zoomIdentity  
           .translate(-xSVG(d0), 0)
           .scale(width / (xSVG(d1) - xSVG(d0))));
      }
    

    相反,我们可以使用补间函数来控制元素的变换并将相同的插值器应用于缩放和平移:

    function zoomToExtent(d0, d1) {
      //get transition start and end values:
      var startScale = d3.zoomTransform(zoomRect.node()).k;
      var startTranslate = d3.zoomTransform(zoomRect.node()).x;
      var endTranslate = -xSVG(d0);
      var endScale = width / (xSVG(d1) - xSVG(d0));
    
      zoomRect.call(zoom).transition()
        .duration(1500)
        .tween("transform", function() {
          var interpolateScale = d3.interpolateNumber(startScale,endScale);
          var interpolateTranslate = d3.interpolateNumber(startTranslate,endTranslate);
    
          return function(t) { 
              var t = d3.zoomIdentity.translate(interpolateTranslate(t),0).scale(interpolateScale(t));
              zoomed(t);
            }
          })
          .on("end", function() {    // update the zoom identity on end:
            d3.select(this).call(zoom.transform, d3.zoomIdentity  
           .translate(endTranslate, 0)
           .scale(endScale));
          })
    
      }
    

    您可能注意到我正在将一个变换值传递给缩放函数,因为没有 d3.event.transform 用于此,我们需要修改缩放函数以使用传递的参数(如果可用),否则回退事件转换:

    function zoomed(transform) {
       var t = transform || d3.event.transform;
       ...
    

    总而言之,这可能看起来像like this


    对于两种过渡方法之间的另一个比较,我创建了一个可以在两个缩放标识之间切换的网格比较:

    var svg = d3.select("body").append("svg")
       .attr("width", 510)
       .attr("height", 310);
       
    var g1 = svg.append("g");
    var g2 = svg.append("g");
       
    var rectangles1 = g1.selectAll()
      .data(d3.range(750))
      .enter()
      .append("rect")
      .attr("x", function(d) { return d%25*20; })
      .attr("y", function(d) { return Math.floor(d/25)*20; })
      .attr("width", 20)
      .attr("height", 20)
      .attr("fill","#ccc")
      .attr("stroke","white")
      .attr("stroke-width", 2);
      
    var rectangles2 = g2.selectAll()
      .data(d3.range(750))
      .enter()
      .append("rect")
      .attr("x", function(d) { return d%25*20; })
      .attr("y", function(d) { return Math.floor(d/25)*20; })
      .attr("width", 20)
      .attr("height", 20)
      .attr("fill","none")
      .attr("stroke","#444")
      .attr("stroke-width", 1);
      
    var startZoom = d3.zoomIdentity
      .translate(-250,-200)
      .scale(4);
    
    var endZoom = d3.zoomIdentity
      .translate(-100,-100)
      .scale(5);
      
    var zoom1 = d3.zoom().on("zoom", function() { g1.attr("transform", d3.event.transform); });
    var zoom2 = d3.zoom().on("zoom", function() { g2.attr("transform", d3.event.transform); });
    
    g1.call(zoom1.transform, startZoom);
    g2.call(zoom2.transform, startZoom);
    
    var toggle = true;
    
    svg.on("click", function() {
      toggle = !toggle;
      g1.transition()
        .duration(5000)
        .call(zoom1.transform, toggle ? startZoom: endZoom)
        
      g2.transition()
        .duration(5000)
        .attr("transform", toggle ? startZoom: endZoom)
        .on("end", function() {
          d3.select(this).call(zoom2.transform,  toggle ? startZoom: endZoom);
        })
        
        
    })
    rect {
      opacity: 0.5;
    }
    <script type="text/javascript" src="https://d3js.org/d3.v5.js"></script>

    【讨论】:

    • @GerardoFurtado,感谢您的编辑和赞美。我总是喜欢详细研究 d3 的一些行为——尽管我可能不会用 Excel (2003) 图表来推销“数据可视化专家”。
    • 感谢您精心制作的答案。 zoomRect.tween 访问器中的return function(t) 中的t 来自哪里?
    • @interwebjill,它是由 d3 使用 function.apply() 应用的 - 这也是在 selection.attr("attribute, function(d,i,j){ }) 中设置 thisdij 的方法示例 - 尽管在这种情况下 d3 将其应用于返回的函数而不是提供的函数。表示过渡进度:0为开始,1为结束。
    猜你喜欢
    • 2018-01-23
    • 1970-01-01
    • 2021-04-07
    • 2021-06-10
    • 2019-07-25
    • 1970-01-01
    • 1970-01-01
    • 2013-05-16
    • 1970-01-01
    相关资源
    最近更新 更多