2013-08-14 16 views
11

私はd3.jsとjqueryをPHPのバックエンド(yiiフレームワークに基づいています)で使用して、ネットワーク上のホストとサービスの現在の状態を表す動的強制有向グラフを作成していますNagiosを使って監視しています。D3 Force Directed Graph Ajaxの更新

このグラフは、root - > hostgroups - > hosts - >サービスを示しています。私は、ノードは、ノードの状態を表示するためのリンクや色に使用されるオブジェクトID(OK =緑、警告を含む次の形式でJSONオブジェクトを返すために

{ 
    "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" 
     } 
    ] 
} 

をサーバ側の機能を作成しました=黄色など)リンクには、ノードのソースオブジェクトIDとターゲットオブジェクトIDがあります。新しいホストが監視システムから追加または削除されるノードおよびリンクは

Iは

  1. 現在のJSONオブジェクトを初期SVG、その後10秒ごとにセットアップ次のコードを取得した変更してもよい
  2. リンク
  3. のマップは、現在のノードとリンクを選択作成し、
  4. 入力リンクが追加されるJSONデータにそれらを結合し、出リンクが
  5. を除去されます
  6. フォースが

    $ .ajaxSetup({キャッシュ:偽})が開始され追加の日付とノードがその塗りつぶしの色を変更し、自分の名前で ツールチップを持っています追加。 幅= 960, 高さ= 500; node = []; link = []; force = d3.layout.force() 。charge(-1000) .linkDistance(1) .size([width、height]);

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

出力の例が正しくコードがループするたびにそれを再起動するように可視化を引き起こすことを例外とノードのすべてのバウンスでNetwork Visualization

で上記の作品のすべてを見ることができます彼らが決着するまで私が必要とするのは、現在のアイテムをそのまま維持することですが、新しいノードやリンクが視覚化に追加され、クリック可能でドラッグ可能なものなどです。

誰でも助けてもらえると永遠に感謝します。 サーバー側は、あなたが生成されているnodeslinks新しいものを伝えることができている、と

+0

を使用しているコードがあります。毎回新しいJSONをリロードするのではなく、サーバー側の変更を確認し、更新時のものとそれらを接続する方法を見つける方法を見つけるべきだと思います。たとえば、_new_ノードとリンクだけを持つJSONを作成し、 'force.on(" tick "、function())を呼び出すと、それらのオブジェクトを' .nodes'と '.links'にプッシュします。 – Joum

+0

私は本当に現在の視覚化オブジェクトをサーバーに戻すことに対処する必要性を避ける方法を望んでいるため、ソリューション全体がはるかに複雑になります。私がd3.jsを見始めたのは、データをd3に渡して、データを入力して終了して、手動でこれを行う必要がなくなりました。代わりの方法はありませんか? – d9705996

+0

実際には、私はあなたのコメントを再読していました.d3.js **は、データに入力されたものと出て来たものを取り除いていません。あなたが提供するデータを使って、あなたがそれを伝えるものを計算します。使用しているデータを変更したい場合は、自分で変更する必要があります。 :) – Joum

答えて

5

私は上記のすべてのアドバイスの混合物を使用して問題の解決策を見つけるために管理している、以下のあなたが実際にデータを再ロードし、レイアウト毎回再計算されているので、たまたま私が

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

多くの援助のためにhttp://bl.ocks.org/benzguo/4370043を使用しました – d9705996

+1

また、不要なセトリングを防ぐために新しいノードを追加または削除するときにforce.start()を実行するブール変数を追加しましたグラフのすべてのチックに – d9705996

+0

こんにちは!数年後:上記のコードで私のディスクからjsonファイルをどのようにフィードできますか?ありがとう:-) – chameau13

1

あなたが実際に限り、バックサーバに何かを渡す必要はありません。次に、d3スクリプト全体をリロードするのではなく、force.on("tick", function())では、追加するdataのサーバから10秒のタイムアウトAJAXコールを行います。nodesまたはlinksです。

たとえば、あなたが最初にサーバーにこのJSONを持っていることを想像:

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

あなたはAJAXを使用して、サーバーからそれを取りに行くとjson = $.parseJSON(json);とそれを解析します。

すると、その代わりに、あなたはsuccessで持っている全体の機能を実行するので、あなたのtimeoutを書き、レイアウトのみを計算を実行します。次に、再度successでサーバーから取得した新しいJSONを解析して、既に存在するforce.nodesforce.linksにそれぞれ_new_nodeslinksを追加します。

私はこれをテストしなかったので、どのように動作するか、実行するかはわかりませんが、一般的なアプローチは実現可能だと思います。

+0

そのような意味が理にかなっています。現在のノードとJSONのノードを比較して、2組のノード/リンクの違いを調べる方法の例はありますか? – d9705996

+0

本当に、申し訳ありません。しかし、新旧を比較するのではなく、単に新しいデータをファイルに出力できませんか?データを追加するだけで済みますか、データを削除する必要がありますか?それは問題を少し複雑にします... – Joum

+0

データを追加したり削除したりする必要があります。現在のノードは異なる属性を持つことがありますので、再レンダリングする必要があります。 – d9705996

2

この回答を確認してください。あなたのノードには、固有の識別子が必要です。

Updating links on a force directed graph from dynamic json data

+0

この例は現在のものよりも優れていますが、このコードを使用してAjaxコールをアタッチすると、アニメーションはゼロから再開されませんが、ノードやリンクが変更されていなくても更新されます。これは、ダイアグラムが全体のステータスビューとして使用され、ウォールボード上に表示され、動きが注意を引くようにデータに何らかの変更がない限り、決済時に移動しないようにダイアグラムを必要とするため、これは受け入れられません変更があった場合にのみ画面が表示されます。 – d9705996

2

私は最近、同じことを実行しようとしました、ここに私が思い付いたソリューションです。最初のデータ一式をlinks.phpにロードしてからnewlinks.phpに更新すると、属性がsenderreceiverのオブジェクトのリストを含むJSONが返されます。この例では、newlinksは毎回新しい送信者を返し、受信者をランダムに選択された古いノードに設定します。

$.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"); 

これは非常に具体的な実装であり、サーバーの出力に応じて必要に応じて穴を埋める必要があります。しかし、それとおもちゃにJSFiddle私はD3バックボーンが正しいと信じて、何あなたは:) を探しているがここにある:http://jsfiddle.net/bTyh5/2/

This code本当に便利だったとここで紹介した部品の一部に影響を与えました。

関連する問題