2013-07-01 42 views
11

Tôi đang sử dụng D3.js để vẽ biểu đồ cây có thể thu gọn như trong example. Nó hoạt động khá tốt, nhưng sơ đồ có thể thay đổi đáng kể về kích thước khi nó đi vào chức năng bình thường của nó (tức là thay vì vài nút tôi có bây giờ, tôi sẽ có thêm lot).D3.js Thu phóng và di chuyển biểu đồ cây có thể thu gọn

Tôi muốn di chuyển vùng SVG, tôi đã thử mọi thứ tôi tìm thấy trực tuyến để hoạt động, nhưng không thành công. Tốt nhất tôi đã làm việc được sử dụng d3.behaviour.drag, trong đó tôi kéo toàn bộ biểu đồ xung quanh. Nó là xa tối ưu và ổn định rất nhiều, nhưng nó là kinda sử dụng được.

Mặc dù vậy, tôi đang cố gắng làm sạch nó một chút và tôi nhận ra rằng d3.behaviour.zoom cũng có thể được sử dụng để pan khu vực SVG, theo tài liệu API.

Câu hỏi: Có ai có thể giải thích cách thích ứng với mã của tôi không?

Tôi muốn có thể pan khu vực SVG có sơ đồ, nếu có thể làm cho nó phản ứng với một số lạm dụng, cụ thể là, cố gắng xoay biểu đồ ra khỏi chế độ xem và cho phép thu phóng đến chế độ xem tối đa kích thước ...

đây là mã của tôi cho đến nay:

var realWidth = window.innerWidth; 
var realHeight = window.innerHeight; 

function load(){ 
    callD3(); 
} 

var m = [40, 240, 40, 240], 
    w = realWidth -m[0] -m[0], 
    h = realHeight -m[0] -m[2], 
    i = 0, 
    root; 

var tree = d3.layout.tree() 
    .size([h, w]); 

var diagonal = d3.svg.diagonal() 
    .projection(function(d) { return [d.y, d.x]; }); 

var vis = d3.select("#box").append("svg:svg") 
    .attr("class","svg_container") 
    .attr("width", w) 
    .attr("height", h) 
    .style("overflow", "scroll") 
    .style("background-color","#EEEEEE") 
    .append("svg:g") 
    .attr("class","drawarea") 
    .attr("transform", "translate(" + m[3] + "," + m[0] + ")") 
    ; 

var botao = d3.select("#form #button"); 

function callD3() { 
//d3.json(filename, function(json) { 
d3.json("D3_NEWCO_tree.json", function(json) { 
    root = json; 
    d3.select("#processName").html(root.text); 
    root.x0 = h/2; 
    root.y0 = 0; 

    botao.on("click", function(){toggle(root); update(root);}); 

    update(root); 
}); 

function update(source) { 
    var duration = d3.event && d3.event.altKey ? 5000 : 500; 

    // Compute the new tree layout. 
    var nodes = tree.nodes(root).reverse(); 

    // Normalize for fixed-depth. 
    nodes.forEach(function(d) { d.y = d.depth * 50; }); 

    // Update the nodes… 
    var node = vis.selectAll("g.node") 
     .data(nodes, function(d) { return d.id || (d.id = ++i); }); 

    // Enter any new nodes at the parent's previous position. 
    var nodeEnter = node.enter().append("svg:g") 
     .attr("class", "node") 
     .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; }) 
     .on("click", function(d) { toggle(d); update(d); }); 

    nodeEnter.append("svg:circle") 
     .attr("r", function(d){ 
        return Math.sqrt((d.part_cc_p*1))+4; 
     }) 
     .attr("class", function(d) { return "level"+d.part_level; }) 
     .style("stroke", function(d){ 
     if(d._children){return "blue";} 
     })  
     ; 

    nodeEnter.append("svg:text") 
     .attr("x", function(d) { return d.children || d._children ? -((Math.sqrt((d.part_cc_p*1))+6)+this.getComputedTextLength()) : Math.sqrt((d.part_cc_p*1))+6; }) 
     .attr("y", function(d) { return d.children || d._children ? -7 : 0; }) 
     .attr("dy", ".35em") 
     .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; }) 
     .text(function(d) { 
     if(d.part_level>0){return d.name;} 
     else 
      if(d.part_multi>1){return "Part " + d.name+ " ["+d.part_multi+"]";} 
      else{return "Part " + d.name;} 
     }) 
     .attr("title", 
      function(d){ 
       var node_type_desc; 
       if(d.part_level!=0){node_type_desc = "Labour";}else{node_type_desc = "Component";} 
       return ("Part Name: "+d.text+"<br/>Part type: "+d.part_type+"<br/>Cost so far: "+d3.round(d.part_cc, 2)+"&euro;<br/>"+"<br/>"+node_type_desc+" cost at this node: "+d3.round(d.part_cost, 2)+"&euro;<br/>"+"Total cost added by this node: "+d3.round(d.part_cost*d.part_multi, 2)+"&euro;<br/>"+"Node multiplicity: "+d.part_multi); 
     }) 
     .style("fill-opacity", 1e-6); 

    // Transition nodes to their new position. 
    var nodeUpdate = node.transition() 
     .duration(duration) 
     .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }); 

    nodeUpdate.select("circle") 
     .attr("r", function(d){ 
      return Math.sqrt((d.part_cc_p*1))+4; 
     }) 
     .attr("class", function(d) { return "level"+d.part_level; }) 
     .style("stroke", function(d){ 
     if(d._children){return "blue";}else{return null;} 
     }) 
     ; 

    nodeUpdate.select("text") 
     .style("fill-opacity", 1); 

    // Transition exiting nodes to the parent's new position. 
    var nodeExit = node.exit().transition() 
     .duration(duration) 
     .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; }) 
     .remove(); 

    nodeExit.select("circle") 
     .attr("r", function(d){ 
      return Math.sqrt((d.part_cc_p*1))+4; 
     }); 

    nodeExit.select("text") 
     .style("fill-opacity", 1e-6); 

    // Update the links… 
    var link = vis.selectAll("path.link") 
     .data(tree.links(nodes), function(d) { return d.target.id; }); 

    // Enter any new links at the parent's previous position. 
    link.enter().insert("svg:path", "g") 
     .attr("class", "link") 
     .attr("d", function(d) { 
     var o = {x: source.x0, y: source.y0}; 
     return diagonal({source: o, target: o}); 
     }) 
    .transition() 
     .duration(duration) 
     .attr("d", diagonal); 

    // Transition links to their new position. 
    link.transition() 
     .duration(duration) 
     .attr("d", diagonal); 

    // Transition exiting nodes to the parent's new position. 
    link.exit().transition() 
     .duration(duration) 
     .attr("d", function(d) { 
     var o = {x: source.x, y: source.y}; 
     return diagonal({source: o, target: o}); 
     }) 
     .remove(); 

    $('svg text').tipsy({ 
     fade:true, 
     gravity: 'nw', 
     html:true 
    }); 

    // Stash the old positions for transition. 
    nodes.forEach(function(d) { 
    d.x0 = d.x; 
    d.y0 = d.y; 
    }); 

    var drag = d3.behavior.drag() 
     .origin(function() { 
      var t = d3.select(this); 
      return {x: t.attr("x"), y: t.attr("y")}; 
     }) 
     .on("drag", dragmove); 

    d3.select(".drawarea").call(drag); 

} 

// Toggle children. 
function toggle(d) { 
    if (d.children) { 
    d._children = d.children; 
    d.children = null; 
    } else { 
    d.children = d._children; 
    d._children = null; 
    } 

} 

function dragmove(){ 
    d3.transition(d3.select(".drawarea")) 
    .attr("transform", "translate(" + d3.event.x +"," + d3.event.y + ")"); 
} 
} 

Trả lời

19

Bạn có thể thấy (đa số) việc thực hiện làm việc ở đây: http://jsfiddle.net/nrabinowitz/fF4L4/2/

các phần quan trọng ở đây:

  • Gọi d3.behavior.zoom() trên phần tử svg. Điều này đòi hỏi các yếu tố svg để có pointer-events: all thiết lập. Bạn cũng có thể gọi điều này trên một subelement, nhưng tôi không thấy lý do nếu bạn muốn toàn bộ điều cần xoay và thu phóng, vì về cơ bản bạn muốn toàn bộ vùng SVG phản hồi các sự kiện pan/zoom.

    d3.select("svg") 
        .call(d3.behavior.zoom() 
         .scaleExtent([0.5, 5]) 
         .on("zoom", zoom)); 
    
  • Đặt scaleExtent tại đây cung cấp cho bạn khả năng giới hạn tỷ lệ thu phóng. Bạn có thể đặt nó thành [1, 1] để vô hiệu hóa thu phóng hoàn toàn hoặc đặt nó theo cách lập trình thành kích thước tối đa của nội dung của bạn, nếu đó là những gì bạn muốn (tôi không chắc chắn chính xác những gì mong muốn ở đây).

  • Chức năng zoom cũng tương tự như chức năng dragmove của bạn, nhưng bao gồm các yếu tố quy mô và thiết lập giới hạn trên chảo bù đắp (như xa như tôi có thể nói, d3 không có bất kỳ built-in panExtent hỗ trợ):

    function zoom() { 
        var scale = d3.event.scale, 
         translation = d3.event.translate, 
         tbound = -h * scale, 
         bbound = h * scale, 
         lbound = (-w + m[1]) * scale, 
         rbound = (w - 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 + ")"); 
    } 
    

    (Thành thật mà nói, tôi không nghĩ rằng tôi có logic ở đây đúng cho ngưỡng trái và phải - điều này dường như không hạn chế kéo đúng khi phóng to. Còn lại như một bài tập cho người đọc :).)

  • Để làm cho mọi việc đơn giản hơn ở đây, nó sẽ giúp bạn có những yếu tố g.drawarea không có một đầu chuyển đổi - vì vậy tôi đã thêm một yếu tố khác g đến giấy gói bên ngoài để thiết lập lề offset:

    vis // snip 
        .append("svg:g") 
        .attr("class","drawarea") 
        .append("svg:g") 
        .attr("transform", "translate(" + m[3] + "," + m[0] + ")"); 
    

Phần còn lại của những thay đổi ở đây chỉ là làm cho mã của bạn hoạt động tốt hơn trong JSFiddle. Có một vài chi tiết còn thiếu ở đây, nhưng hy vọng điều này là đủ để giúp bạn bắt đầu.

+1

Nhiều kudo cho bạn, Mr. Rabinowitz ... Công việc Excelent, cảm ơn bạn! Làm việc như một say mê! Nỗ lực của tôi ở đây đã thất bại chính xác bởi vì aplying 'd3.behavior.zoom' đến trình bao bọc sai - nó chỉ giữ bỏ qua như điên ... Nhưng điều này làm việc thực sự tốt (ngay cả khi một chút chậm hơn/nặng hơn trên trình duyệt) .. . – Joum