这是一个很难的问题 - 我很惊讶地发现没有很好的例子(并且这个问题可能已经被提出之前无分辨率)。根据问题和您想要实现的目标,我认为您的转换过于复杂(也许补间功能可以变得更清晰)。而不是同时使用变换g
以及投影的修改,您只需修改投影即可实现此目的。
目前的方法
当前您平移和缩放g
,这会平移和缩放g
到预定的目的地。点击后,会出现g
定位以使该功能位于中间,然后缩放以展示该功能。因此,g
不再以svg
(因为它已被缩放和平移),换句话说,地球被移动和拉伸,以使特征居中。没有改变任何路径。
此时,您旋转投影,这会根据新的旋转重新计算路径。这会将选定的要素移动到区域的中心g
,不再以svg
- 因为该功能已经集中在svg
,任何运动都会使其偏心。例如,如果删除重新缩放和转换的代码g
,您会注意到您的功能以点击为中心。
潜在的解决方案
您似乎经历了两次转变:
- rotation
- 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 重新计算路径,因此要求不高。
这不是正交投影或圆锥投影所能提供的奢侈,具体取决于所涉及的情况。由于您在更新旋转时无论如何都会重新计算路径,因此比例的更新可能不会导致额外的延迟 - 地理路径生成器无论如何都需要考虑比例和旋转来重新计算和重新绘制路径。