2013-08-14 10 views
11

Używam d3.js i jquery z back-endem PHP (opartym na framework yii), aby utworzyć dynamiczny wykres ukierunkowany na siłę, aby przedstawić aktualny stan hostów i usług w sieci, które monitorują za pomocą Nagios.Aktualizacja D3 Force Directed Graph ajax

Wykres pokazuje root -> hostgroups -> hosts -> services. Stworzyłem funkcję po stronie serwera, aby powrócić obiekt JSON w następującym formacie

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

Węzły zawierać identyfikator obiektu, który jest używany w linkach i kolorów do wyświetlania stanu węzła (OK = zielony, OSTRZEŻENIE = żółty, itp.) Łącza mają identyfikator obiektu źródłowego i identyfikator obiektu docelowego dla węzłów. Węzły i połączenia mogą zmieniać się nowych gospodarze są dodawane lub usuwane z systemu monitorowania

I mają następujące kod, który ustawia początkową SVG i następnie co 10 sekund

  1. Pobiera aktualny obiekt JSON
  2. tworzy mapę powiązań
  3. Wybiera bieżących węzłów i łączy i wiąże je z danymi JSON
  4. Wprowadzanie linki są dodawane i usuwane są linki wychodzące
  5. górę przestarzałe i dodane węzły zmienią kolor wypełnienia i mają etykietka z nazwą dodany
  6. Force rozpoczęła

    $ .ajaxSetup ({cache: false}); szerokość = 960, wysokość = 500; węzeł = []; link = []; siła = d3.layout.force() .char (-1000) .linkDistance (1) .size ([szerokość, wysokość]);

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

Przykładem wyjścia można zobaczyć na Network Visualization

Wszystkie powyższe działa poprawnie z wyjątkiem tego, że za każdym razem kod pętle to przyczynia się do ponownego uruchomienia wizualizacji i węzłów wszystko odrzuceń dopóki nie rozstrzygną. Potrzebuję, aby obecne przedmioty pozostały takie, jakie są, ale do wizualizacji są dodawane nowe węzły i łącza, które można klikać i przeciągać, itp.

Jeśli ktokolwiek może pomóc, będę wdzięczny za wszystko.

+0

tak się dzieje, ponieważ w rzeczywistości przeładowywania danych i przeliczenie każdym razem układ . Zamiast ponownego ładowania nowego JSON za każdym razem, myślę, że powinieneś znaleźć sposób na sprawdzenie zmian po stronie serwera i znaleźć sposób na połączenie ich z aktualizacjami. Na przykład utwórz JSON z tylko nowymi węzłami i łączami i popchnij te obiekty do '.nodes' i' .links' kiedy wywołasz 'force.on (" tick ", function())' – Joum

+0

Naprawdę byłem mając nadzieję na sposób uniknięcia konieczności zajmowania się przekazywaniem bieżących obiektów wizualizacyjnych z powrotem do serwera, ponieważ sprawia, że ​​całe rozwiązanie jest o wiele bardziej złożone. Powodem, dla którego zacząłem patrzeć na d3.js było to, że przekazujesz dane d3 i działa to, co zostało wprowadzone i zakończyło zapisywanie danych, co nie wymaga ręcznego wykonywania tych czynności. Czy nie ma alternatywnych metod? – d9705996

+0

Właściwie ponownie przeczytałem Twój komentarz, a d3.js ** nie ** wykreśla to, co wpisano i usunięto w danych. Oblicza wszystko, co powiesz na podstawie dostarczonych danych. Jeśli chcesz zmienić używane dane, musisz je zmienić samodzielnie. :) – Joum

Odpowiedz

5

udało mi się znaleźć rozwiązanie problemu przy użyciu mieszaniny wszystkich rad powyżej, poniżej jest kod Użyłem

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

Użyłem http://bl.ocks.org/benzguo/4370043 dla dużej pomocy również – d9705996

+1

Dodałem również zmienną boolean, która uruchamia tylko force.start(), gdy nowy węzeł jest dodawany lub usuwany, aby zapobiec niepotrzebnemu rozstrzygnięciu na każdym haczyku wykresu – d9705996

+0

cześć! kilka lat później: czy możesz mi wyjaśnić, w jaki sposób mogę podać plik json z mojego dysku w powyższym kodzie? dzięki :-) – chameau13

1

rzeczywistości nie trzeba przekazać coś z powrotem do serwera, tak długo jak server-side, jesteś w stanie powiedzieć, jakie nowe nodes i links są generowane. Następnie, zamiast przeładowywać cały skrypt d3, ładujesz go raz, aw numerze force.on("tick", function()) wykonasz 10-sekundowe wywołanie AJAX, aby pobrać z serwera nową data, którą chcesz dołączyć, czy to nodes lub links.

Na przykład, wyobraź sobie, że początkowo miał to JSON na serwerze:

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

udać się go z serwera za pomocą technologii AJAX i analizować je z json = $.parseJSON(json);.

Następnie napisać timeout tak, że zamiast biegać cały funkcję masz w success, tylko biegnie po obliczania układ. Następnie ponownie, na success, sparsuj nowy JSON, który otrzymałeś z serwera, i dodaj the_new_ nodes i links do już istniejących force.nodes i force.links odpowiednio.

Należy zauważyć, że nie przetestowałem tego i nie jestem pewien, jak to będzie działać i/lub wykonać, ale myślę, że ogólne podejście jest wykonalne.

+0

Ten rodzaj ma sens.Czy masz przykłady, jak mogę porównać bieżące węzły i węzły w JSON, aby dowiedzieć się różnice między dwoma zestawami węzłów/linków? – d9705996

+0

Niezupełnie, przepraszam. Ale zamiast porównywać nowe/stare, czy nie możesz po prostu wypisać nowych danych do pliku? Czy potrzebujesz tylko dodać dane, czy też musisz usunąć dane? To sprawia, że ​​problem jest trochę bardziej skomplikowany ... – Joum

+0

Dane muszą zostać dodane i usunięte, a jeszcze bardziej skomplikowane, ponieważ obecne węzły mogą mieć różne atrybuty, więc mogą wymagać ponownej odpowiedzi. – d9705996

2

Sprawdź tę odpowiedź. Potrzebujesz unikalnego identyfikatora dla swoich węzłów, który wydaje się mieć.

Updating links on a force directed graph from dynamic json data

+0

Przykład jest lepszy niż to, co mam obecnie, ale kiedy używam tego kodu i dołączam moje wywołanie ajax, animacja nie uruchamia się ponownie od zera, ale nadal się porusza po aktualizacji, nawet jeśli węzły lub łącza nie uległy zmianie. Jest to niedopuszczalne ze względu na to, czego potrzebuję, ponieważ potrzebuję diagramu, aby nie poruszać się po ustaleniu, chyba że zaistnieją jakieś zmiany w danych, ponieważ diagram będzie używany jako ogólny widok stanu i będzie na tablicach ściennych, a ruch zwróci uwagę na ekran powinien pojawić się tylko wtedy, gdy nastąpiła zmiana. – d9705996

2

Niedawno próbował zrobić to samo, tu jest rozwiązanie wymyśliłem. Teraz ładuję pierwszą partię danych za pomocą links.php, a następnie aktualizuję je za pomocą newlinks.php, zwracając JSON z listą obiektów o atrybutach sender i receiver. W tym przykładzie za każdym razem nowe linki zwracają nowego nadawcę, a odbiornik jest losowo wybranym starym węzłem.

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

Jest to bardzo specyficzna implementacja, więc w razie potrzeby powinieneś wypełnić dziury w zależności od wydajności serwera. Ale wierzę, że kręgosłup D3 jest poprawna i co szukasz :) Oto JSFiddle bawić się z nim: http://jsfiddle.net/bTyh5/2/

This code było naprawdę przydatne i zainspirowały niektóre części wprowadzonych tutaj.

Powiązane problemy