d3.js加载后强制布局自动缩放/缩放

时间:2013-04-26 12:34:28

标签: javascript d3.js force-layout

我正在使用Flowingdata.com的this nice force layout来创建网络图。

enter image description here

enter image description here

enter image description here

我的图表目前显示5到750个节点及其关系。它适用于一些自定义更改,以满足我的需求。但有一点我无法开展工作。我有一个viewBox preserveAspectRatio来自动适应它所在的容器。但是根据节点的数量,边缘周围总会有一些节点(主要是顶部和底部)被切断。如果节点很少,它会在中间显示它们周围有一个巨大的空白区域(它是一个大容器)。

有没有办法自动缩放或缩放布局以自动调整?这样一个大的布局会有所缩小,一个小的布局会放大。我有一个缩放事件设置,所以滚动和平移就像一个魅力。但它能自动做到适合内容吗?

d3.js启动代码:

        vis = d3.select(selection)
        .append("svg")
        .attr("viewBox", "0 0 " + width + " " + height )
        .attr("preserveAspectRatio", "xMidYMid meet")
        .attr("pointer-events", "all")
        .call(d3.behavior.zoom().scaleExtent([.1, 3])
                        .on("zoom", redraw)).append('g');

4 个答案:

答案 0 :(得分:12)

日期的所有其他答案都需要访问数据,并通过它进行迭代,因此复杂性至少为O(nodes)。我一直在寻找并找到一种完全基于已经渲染的视觉尺寸的方式,getBBox(),希望O(1)。它的内容或它的布局无关紧要,只是它的大小和父容器的大小。我设法根据http://bl.ocks.org/mbostock/9656675

提出了这个问题
var root = // any svg.select(...) that has a single node like a container group by #id

function lapsedZoomFit(ticks, transitionDuration) {
    for (var i = ticks || 100; i > 0; --i) force.tick();
    force.stop();
    zoomFit(transitionDuration);
}

function zoomFit(transitionDuration) {
    var bounds = root.node().getBBox();
    var parent = root.node().parentElement;
    var fullWidth = parent.clientWidth || parent.parentNode.clientWidth,
        fullHeight = parent.clientHeight || parent.parentNode.clientHeight;
    var width = bounds.width,
        height = bounds.height;
    var midX = bounds.x + width / 2,
        midY = bounds.y + height / 2;
    if (width == 0 || height == 0) return; // nothing to fit
    var scale = 0.85 / Math.max(width / fullWidth, height / fullHeight);
    var translate = [fullWidth / 2 - scale * midX, fullHeight / 2 - scale * midY];

    console.trace("zoomFit", translate, scale);

    root
        .transition()
        .duration(transitionDuration || 0) // milliseconds
        .call(zoom.translate(translate).scale(scale).event);
}

编辑:上述内容适用于D3 v3。在D3 v4和v5中更改了缩放,因此您必须对最后一部分(console.trace下面的代码)进行一些小的更改:

    var transform = d3.zoomIdentity
        .translate(translate[0], translate[1])
        .scale(scale);

    root
        .transition()
        .duration(transitionDuration || 0) // milliseconds
        .call(zoom.transform, transform);

答案 1 :(得分:1)

您的代码应与此类似

   vis = d3.select(selection)
        .append("svg")
        .attr("viewBox", "0 0 " + width + " " + height )
        .attr("preserveAspectRatio", "xMidYMid meet")
        .attr("pointer-events", "all")
        .call(zoomListener)
        .on("zoom", redraw));

   var mainGroup = vis.append('g');

   function zoom() {
        mainGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale +              ")");
   }       

    var zoomListener = d3.behavior.zoom().on("zoom", zoom);

    var xArray = YOUR_NODES.map(function(d) { //YOUR_NODES is something like json.nodes after forse.end()
        return d.x
    });

    var minX = d3.min(xArray);
    var maxX = d3.max(xArray);

    var scaleMin = Math.abs(width / (maxX - minX));
    var startX = (minX) / scaleMin;
    var startY = 50  / scaleMin;

    // Same as in the zoom function
    mainGroup.attr("transform", "translate(" + [startX, startY] + ")scale(" + scaleMin + ")");

    // Initialization start param of zoomListener
    zoomListener.translate([startX, startY]);
    zoomListener.scale(scaleMin);
    zoomListener.scaleExtent([scaleMin, 1])
    vis.call(zoomListener);

此代码仅适用于xAxis。因为"全球圈" svg RX === RY。如果不适合您,那么您可以为yAxis var startY添加相同的逻辑。您还需要考虑圆节点的cr来调整初始坐标。

答案 2 :(得分:1)

Here are two examples:

Force Layout with auto zoom (canvas):

https://vida.io/documents/pT3RDxrjKHLQ8CQxi

Force Layout with auto zoom (SVG)

https://vida.io/documents/9q6B62xQgnZcYYen2

The function that performs scale and draw for SVG:

function scaleAndDraw() {
  var xExtent = d3.extent(d3.values(nodes), function(n) { return n.x; }),
      yExtent = d3.extent(d3.values(nodes), function(n) { return n.y; });

  if ((xExtent[1] - xExtent[0]) > config.width) {
    scaleX = (xExtent[1] - xExtent[0]) / config.width;
    scaleY = (yExtent[1] - yExtent[0]) / config.height;

    scale = 1 / Math.max(scaleX, scaleY);

    translateX = Math.abs(xExtent[0]) * scale;
    translateY = Math.abs(yExtent[0]) * scale,
    svg.attr("transform", "translate(" +
      translateX + "," + translateY + ")" +
      " scale(" + scale + ")");
  }

  draw();
}

答案 3 :(得分:0)

您可以遍历节点;获取所有节点的max和min x和y值,并计算所需的缩放和平移以覆盖SVG大小内的所有viz。

我有一段代码将图形集中到给定节点;这可能会帮助你了解一下。

zs = zoom.scale()
zt = zoom.translate();
dx = (w/2.0/zs) - d.x;
dy = (h/2.0/zs) - d.y;
zoom.translate([dx, dy]);
zoom.scale(zs);

其中w,h是我的SVG画布的宽度和高度,d是我想要居中的节点。

您应该计算平均值x和y,而不是居中于d.x,d.y。并计算缩放比例,使图形的宽度(和高度)适合SVG的宽度(和大小)选择两者中较大的缩放。