带有国家点击和缩放功能的 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 世界地图几乎无法正常工作 的相关文章

  • nvd3 格式化日期始终返回 1970-01-01

    我正在尝试使用构建折线图nvd3 for d3js但我在 x 轴上使用日期域时遇到了一些问题 这是我的代码 data lineChart key key1 values x 2014 04 20 y 6 x 2014 04 13 y 5 x
  • 如何在“object”标签内选择 SVG?

    HTML 页面的内容如下所示 方法如下script js looks var tooltip d3 select body append div style position absolute sty
  • 使用 d3-geo-projection 命令行工具设置自定义投影

    我正在尝试使用 geoproject 在 geojson 文件上设置投影 具体来说 我正在尝试将投影设置为 BCalbers http spatialreference org ref epsg 3005 http spatialrefer
  • d3.js比例符号图:根据数据值设置圆的半径

    我正在遵循这个关于如何使用 d3 js 和 mapbox 制作地图的精彩示例 https franksh com posts d3 mapboxgl https franksh com posts d3 mapboxgl 它工作得很好 除了
  • D3js 从数组而不是文件中获取数据

    我发现了这个优秀的 d3js 图表here http bl ocks org Caged 6476579 但就我而言 我希望此图表从数组而不是 tsv 文件中获取值 我想让它从表 中获取值 我怎样才能做到这一点 因为它使用一个函数来实现这一
  • 根据子节点数量动态调整 d3 树布局的大小

    从这个例子来看http mbostock github com d3 talk 20111018 tree html http mbostock github com d3 talk 20111018 tree html我已经建立了一个d3
  • d3.js 强制布局是否允许动态 linkDistance?

    我使用力布局来表示有向未加权网络 我的灵感来自以下例子 http bl ocks org mbostock 1153292 http bl ocks org mbostock 1153292 我的问题是 在我的情况下 节点之间有更多的链接
  • Javascript 将对象推送为克隆

    我将 d3 用于交互式网络应用程序 我需要绑定的数据在交互过程中发生变化 并且由 JSON 变量中的一些选定对象组成 为此 我在 JSON 变量上使用了映射 并进行了一些查询来选择适当的对象 对象被推送到列表中 并且该列表被绑定为新数据 我
  • D3 强制布局,较大的节点聚集在中心

    我一直在修改将用于标签云的强制布局 每个标签都由一个
  • 如何在D3节点中放置图像?

    到目前为止 我已经创建了这些 D3 节点 用于创建可折叠的层次树 到目前为止 这些节点的颜色为 AA1C1C 深红色 以表明如果您单击它们 它们将扩展到更多节点 我想要做的是在节点中使用图像中的位置 这对于所有用户来说都是一个加号 以知道它
  • 如何在 d3 中使用SimulationLinkDatum和SimulationNodeDatum

    我在使用SimulationLinkDatum 类型时遇到问题 我创建了两个类 Node 和 Link 来实现SimulationNodeDatum 和SimulationLinkDatum 当我尝试使用SimulationLinkDatu
  • 创建链接到 csv 文件的表

    我正在尝试创建一个链接到的表 csv使用 d3 文件 但我得到的只是一个空白网页 即使以克里米亚为例 我也得到一张空白页 我将很高兴得到指导或展示一个可行的例子或关于我做错了什么的建议 如果您询问如何从 CSV 数据创建 HTML 表 这就
  • 设置 D3 力定向图

    致尊敬的读者 我对 javascript 相当陌生 我也遇到过这个问题 我正在尝试实现这个力导向图的修改版本 http mbostock github com d3 ex force html http mbostock github co
  • D3.js 从 file:/// 加载本地数据文件

    我知道D3 js支持使用XHR和JSONP加载数据文件requests https github com mbostock d3 wiki Requests 但就我而言 我将通过从文件系统双击 html 文件来运行它们 这将像file fo
  • 根据 D3 中的属性值对对象进行排序

    我有一系列在 D3 中使用的对象 例如 var cities city London country United Kingdom index 280 city Geneva country Switzerland index 259 ci
  • 如果满足条件,则在另一个转换期间添加并发转换

    我试图在转换运行时添加一个新的转换 条件是如果 bar1 宽度与 bar2 匹配 则条形会更改位置 我使用了transition tween来查看是否满足条件 当第二个转换开始时 第一个转换停止 我希望第一个转换继续运行直到其持续时间结束
  • 时间序列折线图与轴不同步

    本实验基于这个d3官方例子 http bost ocks org mike path 我想要实现的是可视化时间序列数据的最后 x 分钟 我有这个代码的副本jsfiddle http jsfiddle net 225dC 3 单击以添加新数据
  • 如何让d3.js生成svg而不绘制它?

    有没有办法只生成 svg 并将其作为字符串获取 而不实际绘制它 我考虑过将 svg 渲染到隐藏的 div 然后读取内部 html 但是有没有更干净的方法 我认为你可以这样做 var svg d3 select body append svg
  • 如何使用 d3.js 沿 GeoJSON 路径对对象进行动画处理?

    我正在使用 D3 js 从 GeoJSON 文件生成并渲染路径 效果很好 但现在我想沿着该路径为对象设置动画 我知道如何使用 D3 和标准 SVG 来做到这一点 创建过渡并设置其持续时间 对于过渡的每一帧 使用 Complete 查找沿路径
  • SVG 文本在 IE 中消失,直到我单击它

    我在 Internet Explorer Edge 以 Win7 上的为准 中遇到与 SVG 创建和操作相关的问题 在我正在开发的应用程序中 我们使用 d3 生成 SVG 形式的图形 在 Chrome 和 Firefox 中 它们工作得很好

随机推荐