d3.zoom比例的反转顺序和平移 [英] reversing order of d3.zoom scale and translate

查看:84
本文介绍了d3.zoom比例的反转顺序和平移的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果在此示例中单击红色按钮,则:

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


您希望缩放和平移在过渡时以相同的速率同时移动.如果我们使用补间功能,则可以执行此操作.与上述不同,我们不能只使用 transition().attr("transform",newTransfrom),因为您同时也在绘制画布并更新轴.因此,我们需要创建自己的补间函数,该函数可以使用当前的变换和缩放比例,并将其应用于轴,画布和标记.

例如,而不是调用zoom(将使用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){//获取过渡的开始和结束值: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);返回函数(t){var t = d3.zoomIdentity.translate(interpolateTranslate(t),0).scale(interpolateScale(t));缩放(t);}}).on("end",function(){//更新结束时的缩放标识:d3.select(this).call(zoom.transform,d3.zoomIdentity.translate(endTranslate,0).scale(endScale));})} 

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

 功能zoomed(转换){var t =变换||d3.event.transform;... 

总的来说,可能看起来像像这样.


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

  var svg = d3.select("body").append("svg").attr("width",510).attr("height",310);var g1 = svg.append("g");var g2 = svg.append("g");var矩形1 = g1.selectAll().data(d3.range(750)).进入().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矩形2 = g2.selectAll().data(d3.range(750)).进入().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,切换?startZoom:endZoom)g2.transition().duration(5000).attr("transform",切换?startZoom:endZoom).on("end",function(){d3.select(this).call(zoom2.transform,切换?startZoom:endZoom);})}) 

  rect {不透明度:0.5;}  

 < script type ="text/javascript" src ="https://d3js.org/d3.v5.js"></script>  

If you click the red button in this example:

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

you can see that the chart translates so that the circle is in the center and then zooms in to a specified level (reclicking on the button zooms back out). Translating and then zooming in this way leaves a gap on the left that I would rather not have. How might I change the code so that the chart zooms first and then translates to center so that I don't have this gap in the chart?

I have tried reversing the order of the scale and translate in both the zoom definition and the zoomToExtent function but there is no different in effect.

解决方案

The ultimate source of the problem is d3.interpolateZoom. This interpolator has scale interpolate faster than translate - even though they mostly both are transitioning at the same time. The pattern implemented with d3.interpolateZoom is based on this paper.

Because scale and translate both interpolate differently in d3.interpolateZoom, you get a gap in the side of your chart as the scale decreases/increases more rapidly than the translate values.

d3.interpolateZoom is used when you call the zoom on a transition.

However, if you apply a transform directly on a transition using .attr(), the d3 transition will use d3.interpolateString, which will search the start and end strings for corresponding numbers and use d3.interpolateNumber on those. This will apply the same interpolation to both scale and translate.

Using both methods we can compare the discrepancy between d3.interpolateZoom and d3.interpolateString. Below the black rectangle uses d3.interpolateString while the orange rectangle uses d3.interpolateZoom. Click on a rectangle to start the transition:

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>

Where the first rectangle transitions the transform with .attr(), we need to call the zoom afterwards to ensure the zoom has the current transform, we don't need to in this example, but if you wanted to use the zoom after the transform you need to do this

Comparing these two we get:

(Y axis indicates percentage remaining in transition from start attribute to end attribute)


You want scale and translate to move simultaneously at the same rate when transitioning. We can do this if we use a tweening function. Unlike above we can't just use transition().attr("transform",newTransfrom) because you are also drawing canvas and updating the axis. So we'll need to create our own tweening function that can use the current transform and scale, apply it to the axis, canvas, and markers.

For example, rather than calling the zoom (which will use 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))));
  }

Instead, we can use a tweening function which controls the element's transform and applies the same interpolator to scale and translate:

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

  }

You may notice I'm passing a transform value to the zoomed function, since there is no d3.event.transform for this, we need to modify the zoomed function to use the passed parameter if available, otherwise to fall back on the event transform:

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

Altogether, that might look something like this.


For another comparison between the two transitioning methods, I've created a gridded comparison that can be toggled between the two zoom identities:

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>

这篇关于d3.zoom比例的反转顺序和平移的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆