gpt4 book ai didi

d3.js - 带有国家/地区单击和缩放的 d3 世界地图几乎无法正常工作

转载 作者:行者123 更新时间:2023-12-01 12:19:34 25 4
gpt4 key购买 nike

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

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

我的代码有什么问题?

我创建了一个 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>

最佳答案

这是一个困难的问题 - 我很惊讶地看到没有很好的例子(这个问题可能已经提出 previously 没有解决)。根据问题和您要实现的目标,我认为您的转换过于复杂(也许可以使补间功能更清晰)。而不是在 g 上同时使用转换和投影的修改,您只需修改投影即可实现此目的。

当前方法

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

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

潜在解决方案

你似乎在经历了两次转变:

  • 旋转
  • 规模

  • 平移(/translating) 不是您在这里可能想要做的事情,因为当您只想旋转它时,它会移动地球。

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

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

    解决方案实现

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

    我在这里存储当前的旋转和缩放(所以我们可以从这个转换到新的值):
     // Store the current rotation and scale:
    var currentRotate = projection.rotate();
    var currentScale = projection.scale();

    使用您的变量 p为了获得我们要缩放到的特征质心,我通过应用的旋转计算出特征的边界框(但我实际上还没有旋转 map )。使用 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.

    有关此处计算参数的更多信息, see this answer .

    然后我在当前缩放和旋转与目标(下一个)缩放和旋转之间进行补间:
      // 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);

    Updated Plunker
    Bl.ock

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

    替代实现

    如果你真的想保持缩放作为操作 g ,而不是投影,那么您可以实现这一点,但缩放必须在旋转之后 - 因为该特征将在 g 中居中它将以 svg 为中心.看到这个 plunker .您可以在旋转之前计算 bbox,但是如果同时进行两个转换(旋转和缩放),缩放会暂时将地球移离中心。

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

    因为部分路径是隐藏的,所以实际路径可以增加或减少分数,完全出现或消失。到其最终状态的过渡可能并不代表旋转超出地球的地平线时的过渡(实际上它肯定不会),像这样的路径的简单过渡可能会导致伪影,请参阅 this plunker使用修改代码进行可视化演示。为了解决这个问题,我们使用了补间方法 .attrTween .

    .attrTween方法是设置从一条路径到另一条路径的过渡,我们需要同时进行缩放。我们不能使用:
    path.transition()
    .attrTween("d", function()...) // set rotation
    .attr("d", path) // set scale

    缩放 SVG 与缩放投影

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

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

    关于d3.js - 带有国家/地区单击和缩放的 d3 世界地图几乎无法正常工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45595422/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com