2013-08-14 47 views
11

Tôi đang sử dụng d3.js và jquery với một back-end PHP (dựa trên yii framework) để tạo ra một đồ thị động lực đạo để biểu diễn trạng thái hiện tại của máy chủ và dịch vụ trên mạng mà chúng tôi đang theo dõi bằng Nagios.D3 Force Chỉ thị đồ thị ajax cập nhật

Biểu đồ hiển thị gốc -> nhóm máy chủ -> máy chủ -> dịch vụ. Tôi đã tạo ra một chức năng phía máy chủ để trả về một đối tượng JSON theo định dạng sau

{ 
    "nodes": [ 
     { 
      "name": "MaaS", 
      "object_id": 0 
     }, 
     { 
      "name": "Convergence", 
      "object_id": "531", 
      "colour": "#999900" 
     }, 
     { 
      "name": "maas-servers", 
      "object_id": "719", 
      "colour": "#999900" 
     }, 
     { 
      "name": "hrg-cube", 
      "object_id": "400", 
      "colour": "#660033" 
     } 
    ], 
    "links": [ 
     { 
      "source": 0, 
      "target": "531" 
     }, 
     { 
      "source": 0, 
      "target": "719" 
     }, 
     { 
      "source": "719", 
      "target": "400" 
     } 
    ] 
} 

Các nút chứa một đối tượng id được sử dụng trong các liên kết và màu sắc để hiển thị trạng thái của nút (OK = màu xanh lá cây, CẢNH BÁO = màu vàng, vv) Các liên kết có id đối tượng nguồn và id đối tượng mục tiêu cho các nút. Các nút và các liên kết có thể thay đổi khi chủ nhà mới được thêm vào hoặc loại bỏ khỏi hệ thống giám sát

Tôi đã đoạn mã sau đó thiết lập các SVG ban đầu và sau đó mỗi 10 giây

  1. Lấy đối tượng JSON hiện
  2. tạo bản đồ trong các liên kết
  3. Chọn nút hiện hành và các liên kết và liên kết chúng với dữ liệu JSON
  4. liên kết Bước vào được thêm vào và liên kết thoát được loại bỏ
  5. lên ngày và bổ sung các nút sẽ thay đổi màu sắc lấp đầy của họ và có một tooltip với tên của họ thêm
  6. Force được bắt đầu

    $ .ajaxSetup ({cache: false}); chiều rộng = 960, chiều cao = 500; node = []; link = []; force = d3.layout.force() .charge (-1000) .linkDistance (1) .size ([chiều rộng, chiều cao]);

    svg = d3.select("body").append("svg") 
        .attr("width", width) 
        .attr("height", height) 
        .append("g"); 
    
    setInterval(function(){ 
        $.ajax({ 
         url: "<?php echo $url;?>", 
         type: "post", 
         async: false, 
         datatype: "json", 
         success: function(json, textStatus, XMLHttpRequest) 
         { 
          json = $.parseJSON(json); 
    
          var nodeMap = {}; 
          json.nodes.forEach(function(x) { nodeMap[x.object_id] = x; }); 
          json.links = json.links.map(function(x) { 
           return { 
            source: nodeMap[x.source], 
            target: nodeMap[x.target], 
           }; 
          }); 
    
          link = svg.selectAll("line") 
           .data(json.links); 
    
          node = svg.selectAll("circle") 
           .data(json.nodes,function(d){return d.object_id}) 
    
          link.enter().append("line").attr("stroke-width",1).attr('stroke','#999'); 
          link.exit().remove(); 
    
          node.enter().append("circle").attr("r",5); 
          node.exit().remove(); 
    
          node.attr("fill",function(d){return d.colour}); 
    
          node.append("title") 
           .text(function(d) { return d.name; }); 
    
          node.call(force.drag); 
    
          force 
           .nodes(node.data()) 
           .links(link.data()) 
           .start() 
    
          force.on("tick", function() { 
    
           link.attr("x1", function(d) { return d.source.x; }) 
            .attr("y1", function(d) { return d.source.y; }) 
            .attr("x2", function(d) { return d.target.x; }) 
            .attr("y2", function(d) { return d.target.y; }); 
    
           node.attr("cx", function(d) { return d.x = Math.max(5, Math.min(width - 5, d.x)); }) 
            .attr("cy", function(d) { return d.y = Math.max(5, Math.min(height - 5, d.y)); }); 
    
          }); 
         } 
        }); 
    },10000); 
    

Một ví dụ về đầu ra có thể được nhìn thấy ở Network Visualization

Tất cả các công trình trên một cách chính xác với ngoại lệ mà mỗi lần mã vòng nó làm cho trực quan để khởi động lại và các nút tất cả các thư bị trả lại cho đến khi họ ổn định. Những gì tôi cần là cho bất kỳ mặt hàng hiện tại nào để ở, nhưng bất kỳ nút và liên kết mới nào được thêm vào hình ảnh và có thể nhấp và có thể kéo, v.v.

Nếu ai có thể giúp tôi biết ơn mãi mãi.

+0

Điều đó xảy ra bởi vì bạn đang thực sự tải lại dữ liệu và tính toán lại các mọi bố trí . Thay vì tải lại một JSON mới mọi lúc, tôi nghĩ bạn nên tìm cách kiểm tra các thay đổi phía máy chủ và tìm cách kết nối chúng với những gì bạn có khi cập nhật. Ví dụ, tạo một JSON chỉ với các nút _new_ và các liên kết và đẩy các đối tượng đó vào '.nodes' và' .links' khi bạn gọi hàm 'force.on (" tick ", function())' – Joum

+0

Tôi đã thực sự hy vọng cho một cách để tránh phải đối phó với đi qua các đối tượng trực quan hiện tại trở lại máy chủ vì nó làm cho toàn bộ giải pháp phức tạp hơn nhiều. Lý do tôi bắt đầu xem d3.js là bạn chuyển d3 dữ liệu và nó làm việc ra những gì đã nhập và thoát khỏi dữ liệu giúp bạn không phải làm điều này bằng tay. Không có phương pháp thay thế nào? – d9705996

+0

Thực ra, tôi đã đọc lại nhận xét của bạn và d3.js ** không ** làm việc ra những gì đã nhập và exited_ trong dữ liệu. Nó tính toán bất cứ điều gì bạn nói với dữ liệu bạn cung cấp. Nếu bạn muốn thay đổi dữ liệu đang được sử dụng, bạn phải tự thay đổi nó. :) – Joum

Trả lời

5

Tôi đã cố gắng để tìm một giải pháp cho vấn đề sử dụng một hỗn hợp của tất cả những lời khuyên ở trên, dưới đây là đoạn code tôi đã sử dụng

var width = $(document).width(); 
    var height = $(document).height(); 

    var outer = d3.select("#chart") 
     .append("svg:svg") 
      .attr("width", width) 
      .attr("height", height) 
      .attr("pointer-events", "all"); 

    var vis = outer 
     .append('svg:g') 
      .call(d3.behavior.zoom().on("zoom", rescale)) 
      .on("dblclick.zoom", null) 
     .append('svg:g') 

     vis.append('svg:rect') 
      .attr('width', width) 
      .attr('height', height) 
      .attr('fill', 'white'); 

     var force = d3.layout.force() 
      .size([width, height]) 
      .nodes([]) // initialize with a single node 
      .linkDistance(1) 
      .charge(-500) 
      .on("tick", tick); 

     nodes = force.nodes(), 
      links = force.links(); 

     var node = vis.selectAll(".node"), 
      link = vis.selectAll(".link"); 

     redraw(); 

     setInterval(function(){ 
      $.ajax({ 
       url: "<?php echo $url;?>", 
       type: "post", 
       async: false, 
       datatype: "json", 
       success: function(json, textStatus, XMLHttpRequest) 
       { 
        var current_nodes = []; 
        var delete_nodes = []; 
        var json = $.parseJSON(json); 

        $.each(json.nodes, function (i,data){ 

         result = $.grep(nodes, function(e){ return e.object_id == data.object_id; }); 
         if (!result.length) 
         { 
          nodes.push(data); 
         } 
         else 
         { 
          pos = nodes.map(function(e) { return e.object_id; }).indexOf(data.object_id); 
          nodes[pos].colour = data.colour; 
         } 
         current_nodes.push(data.object_id);    
        }); 

        $.each(nodes,function(i,data){ 
         if(current_nodes.indexOf(data.object_id) == -1) 
         { 
          delete_nodes.push(data.index); 
         }  
        }); 
        $.each(delete_nodes,function(i,data){ 
         nodes.splice(data,1); 
        }); 

        var nodeMap = {}; 
        nodes.forEach(function(x) { nodeMap[x.object_id] = x; }); 
        links = json.links.map(function(x) { 
         return { 
          source: nodeMap[x.source], 
          target: nodeMap[x.target], 
          colour: x.colour, 
         }; 
        }); 
        redraw(); 
       } 
      }); 
     },2000); 


     function redraw() 
     { 
      node = node.data(nodes,function(d){ return d.object_id;}); 
      node.enter().insert("circle") 
       .attr("r", 5) 
      node.attr("fill", function(d){return d.colour}) 
      node.exit().remove(); 

      link = link.data(links); 
      link.enter().append("line") 
       .attr("stroke-width",1) 
      link.attr('stroke',function(d){return d.colour}); 
      link.exit().remove(); 
      force.start(); 

     } 

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

      node.attr("cx", function(d) { return Math.round(d.x); }) 
       .attr("cy", function(d) { return Math.round(d.y); }); 
     } 

     function rescale() { 
      trans=d3.event.translate; 
      scale=d3.event.scale; 

      vis.attr("transform", 
       "translate(" + trans + ")" 
       + " scale(" + scale + ")"); 
     } 
+0

Tôi đã sử dụng http://bl.ocks.org/benzguo/4370043 để được trợ giúp thêm – d9705996

+1

Tôi cũng đã thêm một biến boolean chỉ chạy force.start() khi một nút mới được thêm hoặc xóa để ngăn không cho giải quyết không cần thiết trên mọi dấu kiểm của biểu đồ – d9705996

+0

hi! một vài năm sau đó: bạn có thể vui lòng giải thích cho tôi làm thế nào tôi có thể ăn trong một tập tin json từ đĩa của tôi trong bạn mã ở trên? cảm ơn :-) – chameau13

1

Bạn không thực sự cần phải vượt qua bất cứ điều gì trở lại máy chủ, miễn là, server-side, bạn có thể cho biết những gì mới nodeslinks đang được tạo ra. Sau đó, thay vì tải lại toàn bộ tập lệnh d3 của bạn, bạn tải nó một lần và trong force.on("tick", function()), bạn thực hiện cuộc gọi AJAX thời gian chờ 10 giây để truy cập từ máy chủ data mới mà bạn muốn nối thêm, có thể là nodes hoặc links.

Ví dụ, hãy tưởng tượng rằng bạn ban đầu có JSON này trong máy chủ của bạn:

[ 
    { 
     "nodes": [ 
      { 
       "name": "MaaS", 
       "object_id": 0 
      }, 
      { 
       "name": "Convergence", 
       "object_id": "531", 
       "colour": "#999900" 
      } 
     ] 
    }, 
    { 
     "links": [ 
      { 
       "source": 0, 
       "target": "531" 
      } 
     ] 
    } 
] 

Bạn đi lấy nó từ máy chủ của bạn với AJAX và phân tích nó với json = $.parseJSON(json);.

Sau đó, hãy viết timeout để thay vì chạy toàn bộ chức năng bạn có trong success, chỉ chạy sau khi tính bố cục. Sau đó, một lần nữa, trên success, phân tích cú pháp JSON mới mà bạn nhận được từ máy chủ và thêm the_new_ nodeslinks vào tương ứng hiện tại force.nodesforce.links. Vui lòng lưu ý rằng tôi đã không kiểm tra điều này và tôi không chắc nó sẽ hoạt động như thế nào và/hoặc thực hiện, nhưng tôi nghĩ rằng cách tiếp cận chung là khả thi.

+0

Đó là loại có ý nghĩa.Bạn có bất kỳ ví dụ nào về cách tôi có thể so sánh các nút hiện tại và các nút trong JSON để tìm ra sự khác biệt giữa hai bộ nút/liên kết không? – d9705996

+0

Không thực sự, xin lỗi. Nhưng thay vì so sánh mới/cũ, bạn không thể chỉ xuất dữ liệu mới vào một tập tin? Bạn chỉ cần thêm dữ liệu hay bạn cũng cần xóa dữ liệu? Điều đó làm cho vấn đề phức tạp hơn một chút ... – Joum

+0

Dữ liệu cần phải được thêm vào và loại bỏ và để làm cho vấn đề phức tạp hơn, các nút hiện tại có thể có các thuộc tính khác nhau, vì vậy có thể cần được hiển thị lại – d9705996

2

Kiểm tra câu trả lời này. Bạn cần một số nhận dạng duy nhất cho các nút của bạn, mà nó xuất hiện mà bạn có.

Updating links on a force directed graph from dynamic json data

+0

Ví dụ này tốt hơn những gì tôi có hiện tại nhưng khi tôi sử dụng mã này và đính kèm ajax, hoạt ảnh không khởi động lại từ đầu nhưng nó vẫn di chuyển xung quanh khi cập nhật ngay cả khi các nút hoặc liên kết không thay đổi. Điều này không thể chấp nhận được vì tôi cần sơ đồ không di chuyển khi được giải quyết trừ khi có thay đổi trong dữ liệu vì biểu đồ sẽ được sử dụng như chế độ xem trạng thái tổng thể và sẽ nằm trên bảng tường và chuyển động sẽ thu hút sự chú ý màn hình nên chỉ xảy ra khi có thay đổi. – d9705996

2

Gần đây tôi đã cố gắng để làm điều tương tự, đây là giải pháp tôi đã đưa ra. Những gì tôi làm là tải một lô dữ liệu đầu tiên với links.php và sau đó cập nhật chúng với newlinks.php, cả hai đều trả về JSON với một danh sách các đối tượng có thuộc tính senderreceiver. Trong ví dụ này, newlinks trả về một người gửi mới mỗi lần và tôi đặt người nhận là một nút cũ được chọn ngẫu nhiên.

$.post("links.php", function(data) { 
// Functions as an "initializer", loads the first data 
// Then newlinks.php will add more data to this first batch (see below) 
var w = 1400, 
    h = 1400; 

var svg = d3.select("#networkviz") 
      .append("svg") 
      .attr("width", w) 
      .attr("height", h); 

var links = []; 
var nodes = []; 

var force = d3.layout.force() 
        .nodes(nodes) 
        .links(links) 
        .size([w, h]) 
        .linkDistance(50) 
        .charge(-50) 
        .on("tick", tick); 

svg.append("g").attr("class", "links"); 
svg.append("g").attr("class", "nodes"); 

var linkSVG = svg.select(".links").selectAll(".link"), 
    nodeSVG = svg.select(".nodes").selectAll(".node"); 

handleData(data); 
update(); 

// This is the server call 
var interval = 5; // set the frequency of server calls (in seconds) 
setInterval(function() { 
    var currentDate = new Date(); 
    var beforeDate = new Date(currentDate.setSeconds(currentDate.getSeconds()-interval)); 
    $.post("newlinks.php", {begin: beforeDate, end: new Date()}, function(newlinks) { 
     // newlinks.php returns a JSON file with my new transactions (the one that happened between now and 5 seconds ago) 
     if (newlinks.length != 0) { // If nothing happened, then I don't need to do anything, the graph will stay as it was 
      // here I decide to add any new node and never remove any of the old ones 
      // so eventually my graph will grow extra large, but that's up to you to decide what you want to do with your nodes 
      newlinks = JSON.parse(newlinks); 
      // Adds a node to a randomly selected node (completely useless, but a good example) 
      var r = getRandomInt(0, nodes.length-1); 
      newlinks[0].receiver = nodes[r].id; 
      handleData(newlinks); 
      update(); 
     } 
    }); 
}, interval*1000); 

function update() { 
    // enter, update and exit 
    force.start(); 

    linkSVG = linkSVG.data(force.links(), function(d) { return d.source.id+"-"+d.target.id; }); 
    linkSVG.enter().append("line").attr("class", "link").attr("stroke", "#ccc").attr("stroke-width", 2); 
    linkSVG.exit().remove(); 

    var r = d3.scale.sqrt().domain(d3.extent(force.nodes(), function(d) {return d.weight; })).range([5, 20]); 
    var c = d3.scale.sqrt().domain(d3.extent(force.nodes(), function(d) {return d.weight; })).range([0, 270]); 

    nodeSVG = nodeSVG.data(force.nodes(), function(d) { return d.id; }); 
    nodeSVG.enter() 
      .append("circle") 
      .attr("class", "node") 
    // Color of the nodes depends on their weight 
    nodeSVG.attr("r", function(d) { return r(d.weight); }) 
      .attr("fill", function(d) { 
       return "hsl("+c(d.weight)+", 83%, 60%)"; 
      }); 
    nodeSVG.exit().remove();  
} 

function handleData(data) { 
    // This is where you create nodes and links from the data you receive 
    // In my implementation I have a list of transactions with a sender and a receiver that I use as id 
    // You'll have to customize that part depending on your data 
    for (var i = 0, c = data.length; i<c; i++) { 
     var sender = {id: data[i].sender}; 
     var receiver = {id: data[i].receiver}; 
     sender = addNode(sender); 
     receiver = addNode(receiver); 
     addLink({source: sender, target: receiver}); 
    } 
} 

// Checks whether node already exists in nodes or not 
function addNode(node) { 
    var i = nodes.map(function(d) { return d.id; }).indexOf(node.id); 
    if (i == -1) { 
     nodes.push(node); 
     return node; 
    } else { 
     return nodes[i]; 
    } 
} 

// Checks whether link already exists in links or not 
function addLink(link) { 
    if (links.map(function(d) { return d.source.id+"-"+d.target.id; }).indexOf(link.source.id+"-"+link.target.id) == -1 
     && links.map(function(d) { return d.target.id+"-"+d.source.id; }).indexOf(link.source.id+"-"+link.target.id) == -1) 
     links.push(link); 
} 

function tick() { 
    linkSVG.attr("x1", function(d) {return d.source.x;}) 
      .attr("y1", function(d) {return d.source.y;}) 
      .attr("x2", function(d) {return d.target.x;}) 
      .attr("y2", function(d) {return d.target.y;}); 
    nodeSVG.attr("cx", function(d) {return d.x}) 
      .attr("cy", function(d) {return d.y}); 
} 

function getRandomInt(min, max) { 
    return Math.floor(Math.random() * (max - min + 1)) + min; 
} 
}, "json"); 

Đây là triển khai rất cụ thể, do đó bạn nên điền vào các lỗ khi cần thiết tùy thuộc vào đầu ra máy chủ của bạn. Nhưng tôi tin rằng xương sống D3 là chính xác và những gì bạn đang tìm kiếm :) Đây là một JSFiddle để chơi với nó: http://jsfiddle.net/bTyh5/2/

This code thực sự hữu ích và lấy cảm hứng từ một số phần được giới thiệu ở đây.

Các vấn đề liên quan