带有国家点击和缩放功能的 d3 世界地图几乎无法正常工作

2023-12-02

我正在制作一张具有点击缩放功能的世界地图。当点击一个国家时,地图会放大,但该国家并不总是居中——当你点击并重复时,也会发生同样的情况,它似乎永远不会提供相同的结果。

注意:如果禁用过渡功能,缩放和居中确实有效,只有在添加旋转时才会显示不正确。

我的代码有什么问题吗?

我创建了一个plunker为了方便http://plnkr.co/edit/tgIHG76bM3cbBLktjTX0?p=preview

<!DOCTYPE html>
<meta charset="utf-8">
<style>

.background {
  fill: none;
  pointer-events: all;
  stroke:grey;
}

.feature, {
  fill: #ccc;
  cursor: pointer;
}

.feature.active {
  fill: orange;
}

.mesh,.land {
  fill: black;
  stroke: #ddd;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.water {
  fill: #00248F;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script src="//d3js.org/queue.v1.min.js"></script>
<script>

var width = 960,
    height = 600,
    active = d3.select(null);

var projection = d3.geo.orthographic()
    .scale(250)
    .translate([width / 2, height / 2])
    .clipAngle(90);

var path = d3.geo.path()
    .projection(projection);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

svg.append("rect")
    .attr("class", "background")
    .attr("width", width)
    .attr("height", height)
    .on("click", reset);

var g = svg.append("g")
    .style("stroke-width", "1.5px");

var countries;
var countryIDs;

 queue()
  .defer(d3.json, "js/world-110m.json")
  .defer(d3.tsv, "js/world-110m-country-names.tsv")
  .await(ready)

function ready(error, world, countryData) {
  if (error) throw error;

  countries = topojson.feature(world, world.objects.countries).features;
  countryIDs = countryData;

    //Adding water
    g.append("path")
      .datum({type: "Sphere"})
      .attr("class", "water")
      .attr("d", path);

    var world = g.selectAll("path.land")
    .data(countries)
    .enter().append("path")
    .attr("class", "land")
    .attr("d", path)
    .on("click", clicked)

};

function clicked(d) {
  if (active.node() === this) return reset();
  active.classed("active", false);
  active = d3.select(this).classed("active", true);

  var bounds = path.bounds(d),
      dx = bounds[1][0] - bounds[0][0],
      dy = bounds[1][1] - bounds[0][1],
      x = (bounds[0][0] + bounds[1][0]) / 2,
      y = (bounds[0][1] + bounds[1][1]) / 2,
      scale = 0.5 / Math.max(dx / width, dy / height),
      translate = [width / 2 - scale * x, height / 2 - scale * y];

  g.transition()
      .duration(750)
      .style("stroke-width", 1.5 / scale + "px")
      .attr("transform", "translate(" + translate + ")scale(" + scale + ")");

  var countryCode;

  for (i=0;i<countryIDs.length;i++) {
    if(countryIDs[i].id==d.id) {
      countryCode = countryIDs[i];
    }
  }


  var rotate = projection.rotate();
  var focusedCountry = country(countries, countryCode);
  var p = d3.geo.centroid(focusedCountry);


  (function transition() {
    d3.transition()
    .duration(2500)
    .tween("rotate", function() {
      var r = d3.interpolate(projection.rotate(), [-p[0], -p[1]]);

      return function(t) {
        projection.rotate(r(t));
        g.selectAll("path").attr("d", path)
        //.classed("focused", function(d, i) { return d.id == focusedCountry.id ? focused = d : false; });
      };
    })
    })();

    function country(cnt, sel) {
      for(var i = 0, l = cnt.length; i < l; i++) {
        console.log(sel.id)
        if(cnt[i].id == sel.id) {
          return cnt[i];
        }
      }
    };
}

function reset() {
  active.classed("active", false);
  active = d3.select(null);

  g.transition()
      .duration(750)
      .style("stroke-width", "1.5px")
      .attr("transform", "");
}

</script>

这是一个很难的问题 - 我很惊讶地发现没有很好的例子(并且这个问题可能已经被提出之前无分辨率)。根据问题和您想要实现的目标,我认为您的转换过于复杂(也许补间功能可以变得更清晰)。而不是同时使用变换g以及投影的修改,您只需修改投影即可实现此目的。

目前的方法

当前您平移和缩放g,这会平移和缩放g到预定的目的地。点击后,会出现g定位以使该功能位于中间,然后缩放以展示该功能。因此,g不再以svg(因为它已被缩放和平移),换句话说,地球被移动和拉伸,以使特征居中。没有改变任何路径。

此时,您旋转投影,这会根据新的旋转重新计算路径。这会将选定的要素移动到区域的中心g,不再以svg- 因为该功能已经集中在svg,任何运动都会使其偏心。例如,如果删除重新缩放和转换的代码g,您会注意到您的功能以点击为中心。

潜在的解决方案

您似乎经历了两次转变:

  1. rotation
  2. scale

您可能不想在这里执行平移(/平移)操作,因为当您只想旋转地球时,这会移动地球。

旋转只能通过 d3 投影来完成,缩放可以通过对g或在 d3 投影内。因此,仅使用 d3 投影来处理地图转换可能更简单。

另外,当前方法的一个问题是,通过使用 path.bounds 获取 bbox,以导出缩放和平移,您计算的值可能会随着投影更新而改变(投影的类型也会改变方差) 。 例如,如果仅渲染特征的一部分(因为它部分超出地平线),则边界框将与应有的不同,这将导致缩放和平移问题。为了克服我提出的解决方案中的这一限制,请首先旋转地球仪,计算边界,然后缩放到该因子。您可以计算比例,而无需实际更新地球上路径的旋转,只需更新path并稍后过渡绘制的路径.

解决方案实施

我稍微修改了您的代码,我认为最终实现代码更清晰:

我在这里存储当前的旋转和缩放(因此我们可以从该值转换到新值):

 // Store the current rotation and scale:
  var currentRotate = projection.rotate();
  var currentScale = projection.scale();

使用你的变量p为了获得我们要缩放到的特征质心,我通过应用旋转计算出特征的边界框(但我实际上还没有旋转地图)。使用 bbox,我可以获得缩放到所选功能所需的比例:

  projection.rotate([-p[0], -p[1]]);
  path.projection(projection);

  // calculate the scale and translate required:
  var b = path.bounds(d);
  var nextScale = currentScale * 1 / Math.max((b[1][0] - b[0][0]) / (width/2), (b[1][1] - b[0][1]) / (height/2));
  var nextRotate = projection.rotate(); // as projection has already been updated.

有关此处参数计算的更多信息,看到这个答案.

然后我在当前缩放和旋转与目标(下一个)缩放和旋转之间进行补间:

  // Update the map:
  d3.selectAll("path")
   .transition()
   .attrTween("d", function(d) {
      var r = d3.interpolate(currentRotate, nextRotate);
      var s = d3.interpolate(currentScale, nextScale);
        return function(t) {
          projection
            .rotate(r(t))
            .scale(s(t));
          path.projection(projection);
          return path(d);
        }
   })
   .duration(1000);

现在我们同时转换这两个属性:

Plunker

不仅如此,由于我们仅重绘路径,因此不需要修改笔画来缩放g.

其他改进

您可以通过以下方式获得国家/特征的质心:

  // Clicked on feature:
  var p = d3.geo.centroid(d);

更新了Plunker or Bl.ock

您还可以玩弄缓动 - 而不仅仅是使用线性插值 - 例如在此plunker or bl.ock。这可能有助于在过渡期间保持功能的可见性。

替代实施

如果您确实想将缩放保留为对g,而不是投影,那么您可以实现这一点,但缩放必须在旋转之后 - 因为该功能将集中在g将以svg。看到这个plunker。您可以在旋转之前计算 bbox,但如果同时进行两种过渡(旋转和缩放),缩放将暂时使地球偏离中心。

为什么需要使用补间函数来旋转和缩放?

由于部分路径是隐藏的,因此实际路径可能会获得或失去点,完全出现或消失。到最终状态的过渡可能并不代表旋转超出地球地平线时的过渡(事实上它肯定不会),像这样的路径的简单过渡可能会导致伪影,请参阅这个笨蛋使用代码修改进行视觉演示。为了解决这个问题,我们使用补间方法.attrTween.

自从.attrTween方法是设置从一条路径到另一条路径的过渡,我们需要同时进行缩放。我们不能使用:

path.transition()
  .attrTween("d", function()...) // set rotation
  .attr("d", path) // set scale

缩放 SVG 与缩放投影

许多圆柱形投影可以通过直接操作 paths/svg 来平移和缩放,而无需更新投影。由于这不会使用 geoPath 重新计算路径,因此要求不高。

这不是正交投影或圆锥投影所能提供的奢侈,具体取决于所涉及的情况。由于您在更新旋转时无论如何都会重新计算路径,因此比例的更新可能不会导致额外的延迟 - 地理路径生成器无论如何都需要考虑比例和旋转来重新计算和重新绘制路径。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

带有国家点击和缩放功能的 d3 世界地图几乎无法正常工作 的相关文章

  • 用渐变色绘制一个 D3 圆

    如何用渐变颜色画一个圆 比如说 从黄色到蓝色的渐变 通常 要创建黄色圆圈 我们可以使用以下代码 var cdata 50 40 var xscale 40 var xspace 50 var yscale 70 var svg d3 sel
  • 将 D3 svg 保存为高质量图像

    有没有办法将 D3 SVG 图像保存为高质量图像 如果是的话请解释一下 截至目前 我正在使用以下代码将 svg 保存为图像 但我得到的图像质量不高 var canvas1 document createElement canvas canv
  • d3.js:是否可以通过键而不是索引来进行转换?

    我有不同长度的数据数组 x值 年 是有限年数的一部分 例如 var data Year 2008 Value 5 Year 2009 Value 6 or var data Year 2007 Value 8 Year 2009 Value
  • 使用不同颜色的 dc.js 显示原始(有条件)拉丝未拉丝交叉过滤器条

    假设我们有以下 crossfilter dc js 应用程序 虽然这很好 但用户在刷牙时会失去对人群的 参考 我想要图表x y z and a在刷其他图表时保留 基础 柱 也许是不同的颜色 如下所示 我相信这可能需要更新dc renderA
  • 选择 G 元素内的路径并更改样式

    本质上 我试图让除悬停的路径之外的所有路径都变成灰色 而选择的路径则保持其原始颜色 我已经能够将所有其他路径变成灰色 但是我在使用 select this 函数并实际访问我想要更改样式的路径时遇到了问题 看来我实际上已经成功地找到了 g 组
  • 当节点扩展时增加d3中的连接链路长度

    我正在 d3 中研究可折叠力布局 我面临的问题是 当单击节点时 我需要增加节点之间链接的长度 以保持子节点之间的链接距离相同 当分析扩展时 如何增加分析和耀斑之间的距离 保持与子级的距离较小 是的 您可以通过定义一个函数来做到这一点力 链接
  • d3.select 不适用于特殊字符

    我正在使用 d3 js 处理简单的图表 假设下面的 div 是我计划放置 d3 的 svg 容器的地方 div 但是当我使用 d3 select my div chart 我无法选择特定的 div 但是通过使用 java 脚本选择器 它可以
  • d3.js。带条的旋转地球仪

    我正在尝试创建带有像这样的酒吧的旋转地球仪这个例子 http data arts appspot com globe 你可以看我的例子here http jsfiddle net zeleniy jrh5xucs 一切都很顺利 直到酒吧超出
  • Webpack 不包括 ProvidePlugins

    我正在开发一个小型试用 Web 应用程序 它使用 vue webpack 模板 https github com vuejs templates webpack https github com vuejs templates webpac
  • 如何在 d3.scale.ordinal() 中指定域?

    var W 100 var H 200 var data v 4 v 8 v 15 v 16 v 23 v 42 var x d3 scale linear domain 0 max x range 0 W var y d3 scale o
  • 具有水平和垂直组合布局的可折叠树

    我正在尝试在 D3 中创建一个可折叠树 它结合了水平 第一级和第二级 和垂直 3 级 布局 这里有一个jsfiddle http jsfiddle net artemkolotilkin z7tb23Lo 到目前为止我所得到的 除了一件事之
  • NVD3/D3改变y轴最小值

    我目前正在使用 NVD3 制作一些折线图 我想知道是否可以使 y 轴刻度始终从 0 开始 目前它始终从最低的 y 值开始 我尝试过使用tickValues 但我不想更改其他值 我还尝试添加值为 0 的数据点 但这似乎是一种解决方法 它会影响
  • D3.js 中的点图

    我有兴趣创建一个Dot plot 每个数据值都有连续的点 但到目前为止我所管理的是为每个值创建一个点 更清楚地说 假设对于 array1 我希望第一个值创建 5 个圆圈 第二个值创建 4 个圆圈 依此类推 array1 5 4 2 0 3
  • 如何在没有 DOM 的情况下将 javascript 作为 node.js 脚本运行?

    https github com jasondavies d3 cloud https github com jasondavies d3 cloud是一个使用 D3 库的 javascript 文字云 这是一个交互式演示 http www
  • 在 D3 中在外部加载的 svg 图形上绘图

    我已经从 svg 文件加载了外部图形 我想尝试在其上绘图 但不知道如何操作 我的简单 d3 代码在这里
  • 在 d3 中应用转换时出现错误

    我正在尝试对我在 d3 中设计的条形图应用一些过渡效果 这是我的代码 svg selectAll bar data data enter append g attr class bar append rect attr rx barRadi
  • d3.js 更新视觉效果

    我有一个与 d3 js 放在一起的树形图 我通过 getJSON 填充数据 效果很好 但是 我在 setInterval 方法中具有此功能 并且它似乎并没有刷新自身 var treemap d3 layout treemap padding
  • 将文本放置在矩形的中心

    在下面的代码中 文本没有放置在矩形的中心 我使用的是 attr dx x bandwidth 2 attr dy y bandwidth 2 有没有办法将文本中心放置在矩形中 而不是反复试验 目前它看起来像这样 朝底部和朝右 以下是我的代码
  • 在 d3.js 中操纵鼠标悬停在“点击区域”

    我想show and hideSVG 中的一个节点 当鼠标移到 mouseout 问题是我的节点内部的形状是一条宽度只有1 5px的路径 因此在鼠标悬停事件中不容易击中该区域 这对用户体验肯定不方便 我想知道是否有办法做到这一点打击范围更广
  • 在d3.js中将2D形状转换为3D,并根据ANGULAR中的值调整高度

    我正在使用 d3 js v6 创建以下 2D 图表表示的 3D 图表 这个圆圈中有多个正方形 每个正方形都根据值分配了一种颜色 值越大 正方形越暗 现在我想将其转换为 3D 形状 其中当值变高时 只有特定正方形的高度会增加 因此结果在某种程

随机推荐