D3.js在画布和svg中映射缩放行为

D3.js map zoom behaviors in canvas and svg

本文关键字:映射 缩放 svg js D3      更新时间:2023-09-26

感谢阅读。

目标

我想将工作中的SVG点击缩放从Mike Bostock的块与基于画布的系统进行比较。我把正在工作的SVG放在顶部,把画布放在底部。当用户单击上部SVG中的状态时,我希望下部画布元素"跟随"或模仿缩放和平移。例如,在上部SVG中单击Minnesota也会导致下部画布缩放并平移到Minnesota。

问题

我的画布元素在加载topojson后绘制得很好,但它没有动画。我希望它有动画效果。我相信这是因为我不完全理解缩放行为和基于路径的投影。

http://jsfiddle.net/30w8nv4t/2/

function zoomed(d) {
  g.style("stroke-width", 1.5 / d3.event.scale + "px");
  g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
  // the zTranslate and zScale variables appear to be ok,
  // but `d` is null. I'm not sure how to redraw.
  var zTranslate = zoom.translate();
  var zScale = zoom.scale();
  console.log(zTranslate, zScale, d);
  context.clearRect(0, 0, width, height);
  context.beginPath();
  canvasPath(d);
  context.stroke();
}

在这种情况下,d显然为null,因此我无法重新绘制任何内容。我想我的问题可能在zoomed方法或clicked函数中。

原因

我之所以使用这种并排的方法,是因为我想了解路径、投影和缩放行为是如何协同工作的。我很欣赏canvas在SVG上的表现,但缺乏交互性让人望而却步。幸运的是,能够缩放和平移到任意几何图形将我的问题减半。

感谢您的阅读。JSFiddle的链接在这篇文章的顶部。

canvas绘图函数使用纬度/经度坐标的投影值,但您没有在zoom处理程序中更新projectionscaletranslate

获得所追求的行为的一种方法是从zoom处理程序中svg上的transform切换到projection上的transform

我在这个更新的小提琴中做到了这一点:http://jsfiddle.net/30w8nv4t/7/

区别在于:

  1. 更新zoom行为以使用projection translatescale作为默认值,并将scaleExtent值设置为基于projection

    var zoom = d3.behavior.zoom()
        .translate(projection.translate())
        .scale(projection.scale())
        .scaleExtent([projection.scale()/5, projection.scale()*5])
        .on("zoom", zoomed);
    
  2. zoomed函数更新为translate,将scale更新为projection,然后重新绘制基于svg的路径。

    function zoomed(d) {
      //g.style("stroke-width", 1.5 / d3.event.scale + "px");
      //g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
      projection.translate(d3.event.translate).scale(d3.event.scale);
      g.selectAll("path").attr("d", path);
      var zTranslate = zoom.translate();
      var zScale = zoom.scale();
      console.log(zTranslate, zScale, d);
      context.clearRect(0, 0, width, height);
      context.beginPath();
      canvasPath(states);
      context.stroke();
    }
    
  3. 相应地更新您的clicked函数。

    function clicked(d) {
        if (active.node() === this) {
            zoom.scale(500).translate([width/2, height/2]);
            active.classed("active", false);
            active = d3.select(null);
        } else {
            var centroid = path.centroid(d),
                translate = zoom.translate(),
                bounds = path.bounds(d),
                dx = bounds[1][0] - bounds[0][0],
                dy = bounds[1][1] - bounds[0][1],
                scale = .9/ Math.max(dx / width, dy / height);
            zoom.scale(scale * zoom.scale())
                      .translate([
                          translate[0] - centroid[0] * scale + width *  scale / 2, 
                          translate[1] - centroid[1] * scale + height * scale / 2]);
    
            active.classed("active", false);
            active = d3.select(this).classed("active", true);
        }
        zoom.event(svg);
    }
    

    这可能是变化的主要组成部分之一,因为scaletranslate被应用于zoom行为,当它们被应用时,它们必须按当前zoom scale进行缩放。clicked函数随后触发zoomed函数以重新绘制svgcanvas元素。

如您所见,您的canvas绘图代码是正确的。只是的绘制代码使用projection来确定要绘制的点的xy位置,而projection没有被zoom处理程序更新。

对于canvas,也可以有一个单独的projection,并在调用canvas重绘函数之前在zoom处理程序中更新它。我将把它留给读者练习!