如何在 D3 中制作覆盖不同大小节点的凸包

How to make a convex hull covering differently sized nodes in D3

我已经 运行 克服了 D3 的凸包实现的另一个限制:它不会根据节点的大小进行调整。好吧,我没想到它会自动执行此操作,但这似乎是人们正在使用的数据可视化的一个相当标准的方面 D3.js,所以我希望找到一个解决方法。我还没有找到这样的修复程序。

一个明显但不令人满意的解决方案是找到组中最大节点的大小并将其设置为船体的笔划宽度。不幸的是,这看起来很糟糕,然后节点大小变化很大。

我尝试扩展插入假点的想法,以说明具有 1 或 2 个成员的组。在这种情况下,我为每个节点添加了四个点,它们位于节点的 N、S、E 和 W 边界。

var groupPath = function(d) {
var fakePoints = [];  // This adjusts convex hulls for groups with fewer than 3 nodes by adding virtual nodes.
if (d.length == 1 || d.length == 2) {
   fakePoints = [ [d[0].x + 0.001, d[0].y - 0.001],
                  [d[0].x - 0.001, d[0].y + 0.001],
                  [d[0].x - 0.001, d[0].y + 0.001]]; }     
   return "M" + 
   d3.geom.hull(d.map(function(i) { return [
        [(i.x), (i.y + (2 + (4 * i.propertyValue)))],
        [(i.x + (2 + (4 * i.propertyValue))), (i.y)],
        [(i.x), (i.y - (2 + (4 * i.propertyValue)))],
        [(i.x - (2 + (4 * i.propertyValue))), (i.y) ]]; })
      .concat(fakePoints))  //do not forget to append the fakePoints to the input data
      .join("L") 
      + "Z";
};

...其中(2 + (4 * i.propertyValue))是节点的半径。然后我将笔划宽度设置为从节点边缘到凸包外边缘的距离......填充。这似乎是个好主意,但它完全行不通。结果非常令人费解,因为它甚至没有创建这些点的凸包……而且顺序很重要。您可以在 this JSFiddle 中看到它的实际作用。

我首先认为是假点导致了问题,也许它们在某种程度上是这样,但如果你删除它们,凸包不适用于具有 1 或 2 个节点的组......这很奇怪,因为我以为我已经在凸包的输入中添加了 4 个点。一种(可能)可能是我没有以正确的方式添加这四点,但我不知道与此不同的方式是正确的方式。

对于更了解 D3 如何创建凸包的人来说,也许有一个简单的解决方法。也就是说,也许有人可以看到我的方法有什么问题并帮助我修复它,或者也许有人已经有了完全更好的方法来做到这一点。

我之前尝试的问题是由于d3.geom.hull函数中的四个虚点使用了map函数造成的。这创建了嵌套的点数组而不是简单的点列表。我找不到 javascript 的 "flatten" 函数,所以我使用 forEach() 将这些点放在 d3.geom.hull 函数之外,然后像这样输入它们:

var groupPath = function(d) {
    var fakePoints = [];  
    d.forEach(function(element) { fakePoints = fakePoints.concat([  // "0.7071" is the sine and cosine of 45 degree for corner points.
       [(element.x), (element.y + (2 + (4 * element.propertyValue)))],
       [(element.x + 0.7071 * (2 + (4 * element.propertyValue))), (element.y + 0.7071 * (2 + (4 * element.propertyValue)))],
       [(element.x + (2 + (4 * element.propertyValue))), (element.y)],
       [(element.x + 0.7071 * (2 + (4 * element.propertyValue))), (element.y - 0.7071 * (2 + (4 * element.propertyValue)))],
       [(element.x), (element.y - (2 + (4 * element.propertyValue)))],
       [(element.x - 0.7071 * (2 + (4 * element.propertyValue))), (element.y - 0.7071 * (2 + (4 * element.propertyValue)))],
       [(element.x - (2 + (4 * element.propertyValue))), (element.y)],
       [(element.x - 0.7071 * (2 + (4 * element.propertyValue))), (element.y + 0.7071 * (2 + (4 * element.propertyValue)))]
       ]); 
    })
    return "M" + d3.geom.hull( fakePoints ).join("L") + "Z";
};

如您所见,这是通过为每个节点添加 8 个点来实现的,这样凸包从各个方向看都是圆的。这样做还消除了添加 fakePoints 来说明具有 1 或 2 个成员的组的需要。虽然它仍然不如不适应节点大小的普通示例好看,但如果有必要,可以根据您的情况改进美观。

This JSFiddle 有一个包含多层组的工作示例,这样每个组的凸包都会环绕并适应节点的大小。该可视化仍然需要在其他方面进行工作(例如重新绘制船体的边缘并使 link 长度适应组成员身份),但这些是其他问题。这个问题已经解决了,但我仍然愿意看到更好的答案或这个问题的改进版本。