d3.js force layout auto zoom/scale after loading

后端 未结 3 1075
梦毁少年i
梦毁少年i 2020-12-15 05:24

I\'m using this nice force layout from Flowingdata.com to create a network diagram.

\"enter

相关标签:
3条回答
  • 2020-12-15 06:05

    You can iterate over the nodes; get the max and min x and y values for all nodes, and calculate the needed zoom and translation to cover all your viz within the SVG size.

    I have a piece of code that centres the graph to a given node; this might help you get an idea.

    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);
    

    Where w, h are the width and hieight of my SVG canvas, and d the node I want to centre to.

    Instead of centring at d.x,d.y, you should calculate the average x and y. And calculate your zoom-scale that will make the width (and height) of your graph to fit in your SVG's width(and size) Pick the bigger zoom of the two.

    0 讨论(0)
  • 2020-12-15 06:11

    All the other answers to date require access to data, and iterates through it so the complexity is at least O(nodes). I kept looking and found a way that is solely based on already rendered visual size, getBBox() which is hopefully O(1). It doesn't matter what's in it or how it's laid out, just its size and the parent container's size. I managed to whip up this based on 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);
    }
    

    EDIT: The above works in D3 v3. Zoom is changed in D3 v4 and v5, so you have to make some minor changes to the last portion (the code below console.trace):

        var transform = d3.zoomIdentity
            .translate(translate[0], translate[1])
            .scale(scale);
    
        root
            .transition()
            .duration(transitionDuration || 0) // milliseconds
            .call(zoom.transform, transform);
    
    0 讨论(0)
  • 2020-12-15 06:13

    Your code should be similar to this

       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);
    

    This code work only for xAxis. Because "global circle" svg RX === RY. If it not was for you then you can add same logic for yAxis var startY. Also you need to adjust the initial coordinates considering cr of circle nodes.

    0 讨论(0)
提交回复
热议问题