d3.zoom缩放和转换的反转顺序

时间:2018-06-24 23:48:55

标签: d3.js zoom translate

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

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

您会看到图表进行了平移,以使圆圈位于中心,然后放大到指定的级别(重新单击按钮将缩小)。以这种方式进行翻译然后进行缩放会在左侧留下我不希望有的空白。我该如何更改代码,以使图表先放大然后平移到中心,以使图表中没有缝隙?

我尝试过反转比例尺的顺序并转换为zoom定义和zoomToExtent函数,但效果没有什么不同。

1 个答案:

答案 0 :(得分:3)

问题的最终根源是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()转换变换的地方,我们需要随后调用zoom以确保缩放具有当前变换,在此示例中我们不需要这样做,但是如果您想在转换后使用缩放

比较这两个,我们得到:

enter image description here enter image description here

(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) {
  //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));
      })

  }

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

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>