d3.js缩放并将力布局平移到视口中心

d3.js scaling and translating a force layout to the center of a viewport

本文关键字:视口 布局 缩放 js d3      更新时间:2024-01-24

我有一个d3.js力布局,对力计算进行了一些调整。当我使用大型数据集时,有时图形部分或完全在视口之外。我想添加一个命令来重新缩放图形并将其居中到视口内,但遇到了一些问题。

什么有效:

我有一个画布和一个视口:

this.svg_canvas = d3.select("#" + this.container_id)
    .append("svg")
    .attr("width", this.width)
    .attr("height", this.height)
    .call(this.zoom_behavior.bind(this))
;
this.viewport = this.svg_canvas.append("g")
    .attr("id", "viewport")
;

我有缩放和平移视口的缩放行为:

this.zoom_behavior = d3.behavior.zoom()
    .scaleExtent([GraphView.MIN_ZOOM, GraphView.MAX_ZOOM])
    .on('zoom', this._handleZoom.bind(this))
;
GraphView.prototype._handleZoom = function() {
    var translate = d3.event.translate;
    var scale = d3.event.scale;
    this.viewport.attr("transform",
                       "translate(" + translate + ") " +
                       "scale(" + scale + ")");
};

所有这些都起到了应有的作用。

什么不起作用

我添加了一个"重新居中和缩放"方法,该方法应该执行缩放和平移,以将图形带到视口中。它的工作方式是,它首先找到图的范围(通过我的boundingBox()方法),然后用适当的缩放和转换参数调用zoom.behavior

GraphView.prototype.recenterAndScale = function(nodes) {
    var bounding_box = this._boundingBox(nodes || this.force.nodes());
    var viewport = this.zoom_behavior.size();      // viewport [width, height]
    var tx = viewport[0]/2 - bounding_box.x0;
    var ty = viewport[1]/2 - bounding_box.y0;
    var scale = Math.min(viewport[0]/bounding_box.dx, viewport[1]/bounding_box.dy);
    this.zoom_behavior.translate([tx, ty])
        .scale(scale)
        .event(this.svg_canvas)
    ;
};

这行不通。它(通常)将图形定位在视口的边缘。也许我使用了错误的参考资料。网上有没有关于如何"正确"(使用d3习语)做到这一点的例子?

为了完整起见,这里是我对boundingBox()的定义——它返回图中节点的几何中心和范围。据我所知,这一切都很正常:

// Return {dx:, dy:, x0:, y0:} for the given nodes.  If no nodes
// are given, return {dx: 0, dy: 0, x0: 0, y0: 0}
GraphView.prototype._boundingBox = function(nodes) {
    if (nodes.length === 0) {
        return {dx: 0, dy: 0, x0: 0, y0: 0};
    } else {
        var min_x = Number.MAX_VALUE;
        var min_y = Number.MAX_VALUE;
        var max_x = -Number.MAX_VALUE;
        var max_y = -Number.MAX_VALUE;
        nodes.forEach(function(node) {
            if (node.x < min_x) min_x = node.x;
            if (node.x > max_x) max_x = node.x;
            if (node.y < min_y) min_y = node.y;
            if (node.y > max_y) max_y = node.y;
        });
        return {dx: max_x - min_x,
                dy: max_y - min_y,
                x0: (max_x + min_x) / 2.0,
                y0: (max_y + min_y) / 2.0
               };
    }                                  
}

这其实很容易:在计算翻译时,我需要应用缩放的效果。校正后的recenterAndScale()方法为:

GraphView.prototype.recenterAndScale = function(nodes) {
    var bbox = this._boundingBox(nodes || this.force.nodes());
    var viewport = this.zoom_behavior.size();  // => [width, height]
    var scale = Math.min(viewport[0]/bbox.dx, viewport[1]/bbox.dy);
    var tx = viewport[0]/2 - bbox.x0 * scale;  // <<< account for scale
    var ty = viewport[1]/2 - bbox.y0 * scale;  // <<< account for scale
    this.zoom_behavior.translate([tx, ty])
        .scale(scale)
        .event(this.svg_canvas)
    ;
};