Home > OS >  JS D3 Update force Graph with Websocket
JS D3 Update force Graph with Websocket

Time:06-12

Context : I have this Django server that manages devices, i want to show a communication graph between these devices, i've decide to use D3 force graph for this purpose, the Django server will send a json through Redis with a websocket, i want the client to read the json and print the graph.
So far i've been able to print static graph, but i can't manage to update it live.

Usefull link :

  • Core code from This example.
  • Tried to follow This, but i don't think it's the right direction.

Goal : Update a Force graph in real time using websocket.

My JS code :


var graph = {
  "nodes": [
            {"id": "Agent_1", "group": 1},
            {"id": "Agent_2", "group": 2},
            {"id": "Agent_3", "group": 1},
            {"id": "Agent_4", "group": 3}
        ],
  "links": []
};


const comSocket = new WebSocket(
  'ws://'
    window.location.host
    '/ws/com/'
);

comSocket.onmessage = function (e) {
  graph = JSON.parse(e.data).message;
  console.log(graph);

  simulation.nodes(graph.nodes);
  simulation.force("link").links(graph.links);
  simulation.alpha(1).restart();
  };


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

var color = d3.scaleOrdinal(d3.schemeCategory20);

var simulation = d3.forceSimulation()
    .force("link", d3.forceLink().id(function(d) { return d.id; }))
    .force("charge", d3.forceManyBody().strength(-2500))
    .force("center", d3.forceCenter(width / 2, height / 2));

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

  var node = svg.append("g")
      .attr("class", "nodes")
    .selectAll("g")
    .data(graph.nodes)
    .enter().append("g")

  var circles = node.append("circle")
    .attr("r", 20)
    .attr("fill", function(d) { return color(d.group); });

  var lables = node.append("text").text(function(d) {return d.id;}).attr('x', 16).attr('y', 13);

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

  simulation.nodes(graph.nodes).on("tick", ticked);
  simulation.force("link").links(graph.links);

  function ticked() {
    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("transform", function(d) {
          return "translate("   d.x   ","   d.y   ")";
        })
  };

Using the above code it produce this error :

Uncaught TypeError: Cannot read properties of undefined (reading 'length')

at the line :

  simulation.nodes(graph.nodes);

in onmessage()

The data value is a json with the same structure as var graph (line 1). So i don't know why it can initialize the graph correctly but canno't refresh with the same value.. :

{'nodes': [{'id': 'Agent_0', 'group': 1}, {'id': 'Agent_1', 'group': 2}, {'id': 'Agent_2', 'group': 1}, {'id': 'Agent_3', 'group': 3}], 'links': [{'source': 'Agent_0', 'target': 'Agent_2', 'value': 1}, {'source': 'Agent_0', 'target': 'Agent_1', 'value': 3}, {'source': 'Agent_0', 'target': 'Agent_3', 'value': 5}, {'source': 'Agent_1', 'target': 'Agent_3', 'value': 3}, {'source': 'Agent_2', 'target': 'Agent_3', 'value': 5}, {'source': 'Agent_1', 'target': 'Agent_2', 'value': 5}]}

CodePudding user response:

It was a server side issue, wrong type was sent.

In the end i've also update the code to latest version (only color needed to be updated). Here's the final working version :

var graph = {
 "nodes": [
           {"id": "Agent_0", "group": 1},
           {"id": "Agent_1", "group": 2},
           {"id": "Agent_2", "group": 1},
           {"id": "Agent_3", "group": 3}
       ],
 "links": []
};

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

var color = d3.schemeCategory10;

var simulation = d3.forceSimulation()
   .force("link", d3.forceLink().id(function(d) { return d.id; }))
   .force("charge", d3.forceManyBody().strength(-2500))
   .force("center", d3.forceCenter(width / 2, height / 2));

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

 var node = svg.append("g").attr("class", "nodes").selectAll("g").data(graph.nodes).enter().append("g")
 var circles = node.append("circle").attr("r", 20).attr("fill", function(d) { return color[d.group]; });
 var lables = node.append("text").text(function(d) {return d.id;}).attr('x', 16).attr('y', 13);

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

 simulation.nodes(graph.nodes).on("tick", ticked);
 simulation.force("link").links(graph.links);

 function ticked() {
   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("transform", function(d) {
         return "translate("   d.x   ","   d.y   ")";
       })
 };

const comSocket = new WebSocket(
 'ws://'
   window.location.host
   '/ws/com/'
);

comSocket.onmessage = function (e) {
 graph = JSON.parse(e.data).message;
 console.log(graph);
 console.log(typeof graph);

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

 node = svg.append("g").attr("class", "nodes").selectAll("g").data(graph.nodes).enter().append("g")
 circles = node.append("circle").attr("r", 20).attr("fill", function(d) { return color[d.group]; });
 lables = node.append("text").text(function(d) {return d.id;}).attr('x', 16).attr('y', 13);

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

 simulation.nodes(graph.nodes).on("tick", ticked);
 simulation.force("link").links(graph.links);
 simulation.alpha(1).restart();
 };

  • Related