D3js地图标记碰撞检测

时间:2015-02-28 10:44:47

标签: javascript svg d3.js

是否有一个漂浮在碰撞检测周围的例子,通过操纵半径而不是x,y坐标来避免碰撞?我知道Mike Bostock和其他人组合在一起的例子,但我没有使用力图,我的点是地理的,不能操纵它们的坐标。

我最好的猜测是从半径为0的圆开始,迭代它们并增加它们各自的半径,只要它们不与另一个圆相撞。我认为这会产生一种奇妙的可视化效果,但我不确定如何有效地确定一个圆是否与另一个圆相撞。


带有内联D3js的地图的JSBin(JavaScript选项卡只是一个600kb的GeoJSON数据集):http://jsbin.com/tapuhefamu/1/edit?html,output

注意缩放时标记是如何重叠的,它在小提琴中看起来不是什么大问题(只是进一步放大,对吧?)但是我正在使用的地图只有少数几个群集了~2,000个针脚单击时需要显示信息DIV。由于重叠,某些引脚几乎完全被遮挡,无法与之交互。

1 个答案:

答案 0 :(得分:2)

我为你编写了一些东西。检测碰撞非常容易,基本上计算两个中心点之间的距离,如果距离小于加在一起的两个半径,那么它们必然会发生碰撞。

我遇到了jsbin的一些问题,所以我把它变成了一个要点,你可以在http://bl.ocks.org/benlyall/6a81499abf7a0e2ad304查看

有趣的是:

  1. 添加radiusStep参数 - 使用此参数来平衡迭代次数与节点之间潜在重叠量之间的权衡。

    radiusStep = 0.01,
    
  2. 从缩放处理程序中删除半径缩放:

    zoom = d3.behavior.zoom().on("zoom",function() {
            g.attr("transform","translate("+ d3.event.translate.join(",")+")scale("+d3.event.scale+")");
            //g.selectAll("circle")
            //.attr("r", nodeRadius / d3.event.scale);
            g.selectAll("path")
                .style('stroke-width', countyBorderWidth / d3.event.scale )
                .attr("d", path.projection(projection));
    }),
    
  3. 创建一个新结构,以跟踪节点是否与另一个节点相撞,半径以及x和y位置(预先根据您的投影计算)

    nodes = nodeGeoData.map(function(n) {
        var pos = projection(n);
        return {
            collided: false,
            x: pos[0],
            y: pos[1],
            r: 0
        };
    });
    
  4. 两个新功能,用于检测碰撞并扩展半径,直到检测到碰撞。

    function tick() {
        nodes.forEach(collided);
    
        nodes.forEach(function(n) {
            if (!n.collided) {
                n.r += radiusStep;
                if (n.r > nodeRadius) {
                    n.r = nodeRadius;
                    n.collided = true;
                }
            }
        });
    }
    

    tick函数首先在每个节点上调用collide以确定它是否与任何其他节点发生冲突。然后,它会将未发生碰撞的任何节点的半径增加radiusStep。如果半径大于nodeRadius参数,则将半径设置为该值,并将其标记为碰撞以阻止其增加。

    function collided(node, i) {
        if (node.collided) return;
    
        nodes.forEach(function(n, j) {
            if (n !== node) {
                var dx = node.x - n.x, dy = node.y - n.y,
                    l = Math.sqrt(dx*dx+dy*dy);
    
                if (l < node.r + n.r) {
                    node.collided = true;
                    n.collided = true;
                }
            }
        });
    }
    

    碰撞函数检查每个节点是否与任何其他节点发生碰撞(出于显而易见的原因除外)。如果它检测到冲突,则比较中的两个节点都标记为冲突。为了检测实际碰撞,计算x和y位置的差异,然后使用毕达哥拉斯计算它们之间的距离。如果该距离小于加在一起的两个节点的半径,则发生碰撞。

  5. 更新drawMap函数以在绘制节点之前计算半径。

    while (nodes.filter(function(n) { return n.collided; }).length < nodes.length) {
        tick();
    }
    

    这基本上只调用tick函数,直到所有节点都标记为冲突。

  6. drawNodes函数已更新为使用新的nodes数据结构:

    function drawNodes(nodes) {
        g.selectAll('circle').data(nodes).enter().append("circle")
            .attr("cx", function (d) { return d.x; })
            .attr("cy", function (d) { return d.y; })
            .attr("r", function(d, i) { return d.r; })
            .attr("class", "map-marker");
    }
    

    此处的更改仅引用先前创建的每个节点对象的xyr属性。

  7. 虽然这很有效,但似乎非常有效,但由于tickcollided函数的组合为O(n^2),因此很容易陷入困境,很快就会陷入困境。