颠倒 d3.zoom 缩放和平移的顺序

int*_*ill 2 translate zooming d3.js

如果您单击此示例中的红色按钮:

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

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

我曾尝试在缩放定义和 zoomToExtent 函数中颠倒比例和平移的顺序,但效果没有什么不同。

And*_*eid 5

问题的最终根源是 d3.interpolateZoom。这个插值器的比例插值比翻译快——即使它们大多同时转换。实现的模式d3.interpolateZoom基于这篇论文

由于 scale 和 translate 两者在 中的插值方式不同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)
      

});
Run Code Online (Sandbox Code Playgroud)
<script type="text/javascript" src="https://d3js.org/d3.v5.js"></script>
Run Code Online (Sandbox Code Playgroud)

在第一个矩形使用 .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))));
  }
Run Code Online (Sandbox Code Playgroud)

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

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));
      })

  }
Run Code Online (Sandbox Code Playgroud)

您可能会注意到我向 zoomed 函数传递了一个转换值,因为没有 d3.event.transform 为此,我们需要修改 zoomed 函数以使用传递的参数(如果可用),否则回退到事件转换:

function zoomed(transform) {
   var t = transform || d3.event.transform;
   ...
Run Code Online (Sandbox Code Playgroud)

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


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

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))));
  }
Run Code Online (Sandbox Code Playgroud)
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));
      })

  }
Run Code Online (Sandbox Code Playgroud)
function zoomed(transform) {
   var t = transform || d3.event.transform;
   ...
Run Code Online (Sandbox Code Playgroud)