【问题标题】:Is there a way to zoom into a D3 force layout graph?有没有办法放大 D3 力布局图?
【发布时间】:2011-12-13 20:37:55
【问题描述】:

D3 具有强制导向布局here。有没有办法给这个图添加缩放?目前,我能够捕获鼠标滚轮事件,但不确定如何编写重绘函数本身。有什么建议吗?

    var vis = d3.select("#graph")
        .append("svg:svg")
        .call(d3.behavior.zoom().on("zoom", redraw)) // <-- redraw function
        .attr("width", w)
        .attr("height", h);

【问题讨论】:

标签: javascript jquery d3.js zooming force-layout


【解决方案1】:

如果您想在不更改节点大小的情况下缩放和平移强制布局,请尝试以下操作。 您也可以拖动节点而不会颤抖。 此代码基于原始力布局示例。节点和链接数据请参考原始样本数据。 http://bl.ocks.org/mbostock/4062045

请注意变量xScale 和yScale,函数dragstarted()、dragged() 和dragended()。 函数 tick() 也发生了变化。

你可以在http://steelblue.tistory.com/9看到结果 网站上的语言是韩语。但是,您可以在页面上的第三个示例中轻松找到结果。

var graph = {
    "nodes": [
      { "name": "Myriel", "group": 1 },
      { "name": "Napoleon", "group": 1 },
      // ......
      { "name": "Mme.Hucheloup", "group": 8 }
    ],
    "links": [
      { "source": 1, "target": 0, "value": 1 },
      { "source": 2, "target": 0, "value": 8 },
    // .......
      { "source": 76, "target": 58, "value": 1 }
    ]
};
var width = 640,
    height = 400;
 var color = d3.scale.category20();



var xScale = d3.scale.linear()
        .domain([0, width])
         .range([0, width]);

var yScale = d3.scale.linear()
    .domain([0, height])
   .range([0, height]);
var zoomer = d3.behavior.zoom().x(xScale).y(yScale).scaleExtent([0.1, 8]).on("zoom", zoom);
function zoom() {

    tick(); 
};

var drag = d3.behavior.drag()
        .origin(function (d) { return d; })
         .on("dragstart", dragstarted)
        .on("drag", dragged)
        .on("dragend", dragended);

function dragstarted(d) {
    d3.event.sourceEvent.stopPropagation();

    d.fixed |= 2;         
}
function dragged(d) {

    var mouse = d3.mouse(svg.node());
    d.x = xScale.invert(mouse[0]);
    d.y = yScale.invert(mouse[1]);
    d.px = d.x;         
    d.py = d.y;
    force.resume();
}

function dragended(d) {

    d.fixed &= ~6;           }

var force = d3.layout.force()
    .charge(-120)
    .linkDistance(30)
    .size([width, height]);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

svg.call(zoomer);

    force
        .nodes(graph.nodes)
        .links(graph.links)
        .start();

    var link = svg.selectAll(".link")
        .data(graph.links)
      .enter().append("line")
        .attr("class", "link")
        .style("stroke-width", function (d) { return Math.sqrt(d.value); });

    var node = svg.selectAll(".node")
        .data(graph.nodes)
      .enter().append("circle")
        .attr("class", "node")
        .attr("r", 5)
        .style("fill", function (d) { return color(d.group); })
        .call(drag);

    node.append("title")
        .text(function (d) { return d.name; });

    force.on("tick",tick);

function tick(){            
        link.attr("x1", function (d) { return  xScale(d.source.x); })
            .attr("y1", function (d) { return yScale(d.source.y);  })
            .attr("x2", function (d) { return xScale(d.target.x); })
            .attr("y2", function (d) { return yScale(d.target.y); });

        node.attr("transform", function (d) {
            return "translate(" + xScale(d.x) + "," + yScale(d.y) + ")";
        });


    };

【讨论】:

    【解决方案2】:

    2014 年 6 月 4 日更新

    另请参阅 Mike Bostock's answer here 以了解 D3 v.3 和 related example 中的更改。我认为这可能会取代下面的答案。

    2014 年 2 月 18 日更新

    如果您希望整个 SVG 进行平移和缩放,我认为 @ahaarnos 的答案更可取。我下面的答案中嵌套的g 元素只有在同一个 SVG 中有非缩放元素时才真正需要(原始问题中不是这种情况)。如果您确实将该行为应用于g 元素,则需要背景rect 或类似元素来确保g 接收指针事件。

    原答案

    我根据zoom-pan-transform 示例进行了这项工作 - 您可以在此处查看我的 jsFiddle:http://jsfiddle.net/nrabinowitz/QMKm3/

    它比我希望的要复杂一些 - 你必须嵌套几个 g 元素才能让它工作,将 SVG 的 pointer-events 属性设置为 all,然后附加一个背景矩形来接收指针事件(否则它仅在指针位于节点或链接上时才有效)。 redraw函数比较简单,只是在最里面的g上设置一个transform:

    var vis = d3.select("#chart")
      .append("svg:svg")
        .attr("width", w)
        .attr("height", h)
        .attr("pointer-events", "all")
      .append('svg:g')
        .call(d3.behavior.zoom().on("zoom", redraw))
      .append('svg:g');
    
    vis.append('svg:rect')
        .attr('width', w)
        .attr('height', h)
        .attr('fill', 'white');
    
    function redraw() {
      console.log("here", d3.event.translate, d3.event.scale);
      vis.attr("transform",
          "translate(" + d3.event.translate + ")"
          + " scale(" + d3.event.scale + ")");
    }
    

    这有效地缩放了整个 SVG,因此它也缩放了笔画宽度,就像放大图像一样。

    还有一个example 说明了类似的技术。

    【讨论】:

    • @Ogg - 我不确定你的意思 - jsFiddle 只是在 iFrame 中呈现你的结果,而不是某种自定义浏览器,所以你看到的 真实的浏览器行为。 jsFiddle 确实添加了一些东西,例如body 标记,所以我建议查看帧源并查看您缺少的内容。
    • @EricStob - 这可能是一个新问题。但是请参阅jsfiddle.net/56RDx/2 - 这只是通过缩放比例的倒数重新调整字体大小。
    • @ajmartin - 见zoom.scaleExtent()
    • 当使用 d3 的version 3 时,拖动单个节点的功能在此示例中不起作用。相反,它会平移整个图形,就好像您没有单击某个节点一样。这适用于version 2,但我需要 v3 上的功能。有什么想法吗?
    • 这里是 D3 v3 的解决方案:stackoverflow.com/questions/17953106/…
    【解决方案3】:

    提供的答案在 D3 v2 中有效,但在 v3 中无效。我已将响应合成为一个干净的解决方案,并使用此处提供的答案解决了 v3 问题:Why does d3.js v3 break my force graph when implementing zooming when v2 doesn't?

    首先是主要代码。这是@ahaarnos 答案的清理版本:

        var svg = d3.select("body")
            .append("svg")
            .attr("width", width)
            .attr("height", height)
                .call(d3.behavior.zoom().on("zoom", redraw))
            .append('g');
    
        function redraw() {
          svg.attr("transform",
              "translate(" + d3.event.translate + ")"
              + " scale(" + d3.event.scale + ")");
        }   
    

    现在您有了平移和缩放功能,但您将无法拖动节点,因为平移功能将覆盖拖动功能。所以我们需要这样做:

    var drag = force.stop().drag()
    .on("dragstart", function(d) {
        d3.event.sourceEvent.stopPropagation(); // to prevent pan functionality from 
                                                //overriding node drag functionality.
        // put any other 'dragstart' actions here
    });
    

    这里是 @nrabinowitz 的小提琴修改为使用这种更清晰的缩放实现,但说明了 D3v3 如何打破节点拖动:http://jsfiddle.net/QMKm3/718/

    下面是同一个小提琴修改为与 D3v3 一起使用:http://jsfiddle.net/QMKm3/719/

    【讨论】:

      【解决方案4】:

      我得到了带有缩放选项的 D3 力有向图的解决方案。

          var m = [40, 240, 40, 240],
          width = 960,
          height = 700,
          root;
      var svg = d3.select("body").append("svg")
          .attr("class", "svg_container")
          .attr("width", width)
          .attr("height", height)
          .style("overflow", "scroll")
          .style("background-color", "#EEEEEE")
          .append("svg:g")
          .attr("class", "drawarea")
          .append("svg:g")
          .attr("transform", "translate(" + m[3] + "," + m[0] + ")");
      
      //applying zoom in&out for svg
      d3.select("svg") 
      .call(d3.behavior.zoom()
          .scaleExtent([0.5, 5])
          .on("zoom", zoom));
      
      //zooming 
      function zoom() { //zoom in&out function 
          var scale = d3.event.scale,
              translation = d3.event.translate,
              tbound = -height * scale,
              bbound = height * scale,
              lbound = (-width + m[1]) * scale,
              rbound = (width - m[3]) * scale;
          // limit translation to thresholds
          translation = [
              Math.max(Math.min(translation[0], rbound), lbound),
              Math.max(Math.min(translation[1], bbound), tbound)
          ];
          d3.select(".drawarea")
              .attr("transform", "translate(" + translation + ")" +
                  " scale(" + scale + ")");
      }
      

      【讨论】:

        【解决方案5】:

        我的图表可以在没有第二个“svg:g”附加的情况下工作。

        [...].attr("pointer-events", "all")
             .attr("width", width2)
             .attr("height", height2)
             .append('svg:g')
             .call(d3.behavior.zoom().on("zoom", redraw));
        

        其余的都一样。

        【讨论】:

        • 但没有矩形:不能平移(只能缩放)
        【解决方案6】:

        为什么要嵌套&lt;g&gt;

        下面这段代码对我来说效果很好(只有一个&lt;g&gt;,没有随机的大白&lt;rect&gt;

        var svg = d3.select("body")
            .append("svg")
              .attr({
                "width": "100%",
                "height": "100%"
              })
              .attr("viewBox", "0 0 " + width + " " + height )
              .attr("preserveAspectRatio", "xMidYMid meet")
              .attr("pointer-events", "all")
            .call(d3.behavior.zoom().on("zoom", redraw));
        
        var vis = svg
            .append('svg:g');
        
        function redraw() {
          vis.attr("transform",
              "translate(" + d3.event.translate + ")"
              + " scale(" + d3.event.scale + ")");
        }
        

        然后将 svg 中的所有元素附加到 vis 元素。

        【讨论】:

        • 您可能会丢失属性“viewBox”、“preserveAspectRatio”和“pointer-events”,但它仍然可以工作吗?
        • @notan3xit 是对的,不需要 viewBox、preserveAspectRatio 和指针事件。关键是将transformation 属性应用于g 元素,not 应用于svg 元素。
        • 似乎不适用于 D3 v3,或者说缩放功能仍然有效,但移动单个节点的能力丢失了。 @nrabinowitz 解决方案表现出同样的问题。这是 nrabinowitz 的小提琴更新为使用 ahaarnos 的解决方案:jsfiddle.net/QMKm3/716,这是相同的小提琴更新为使用 D3v3 来说明问题:jsfiddle.net/QMKm3/717
        • 将缩放行为添加到 SVG 元素的完美想法,我不知道你能做到这一点,因此总是使用烦人的背景矩形。在 SVG 上添加行为至少在现代版本的 Chrome、FF 和 Opera 中有效。
        猜你喜欢
        • 2014-12-15
        • 2015-04-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-28
        • 1970-01-01
        相关资源
        最近更新 更多