JS D3 Update force Graph with Websocket - javascript

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

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

D3v6 nested graph - nested join()?

I want to visualize the "children" insight each node. I guess the D3v6 .join() function can be nested. Unfortunately I can´t find any example. The snippet below contains an outerGraph with 3 nodes and children as attribute. So far those children aren´t used yet.
The innerGraph instead visualize the small nodes which will be obsolete as soon as the children approach is working. Another Idea would be to work with those two graphs and create a gravity / cluster, which will be the parent.
Goal: Either utilize the children attribute or combine both graphs with the help of an cluster /gravity or even nested join(). I am appreciating any hint / tip. The visuals result should be:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3v6 nested nodes</title>
<script src="https://d3js.org/d3.v6.min.js"></script>
</head>
<style>
body {
background-color: whitesmoke;
}
</style>
<body>
<script>
var svg = d3.select("body").append("svg")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight);
var width = window.innerWidth
var height = window.innerHeight
var outerLinkContainer = svg.append("g").attr("class", "outerLinkContainer")
var outerNodeContainer = svg.append("g").attr("class", "outerNodeContainer")
var innerLinkContainer = svg.append("g").attr("class", "innerLinkContainer")
var innerNodeContainer = svg.append("g").attr("class", "innerNodeContainer")
//###############################################
//############# outer force Layouts #############
//###############################################
var outerGraph = {
"nodes": [
{
"id": "A",
"children": [
{
"id": "A1"
},
{
"id": "A2"
}
]
},
{
"id": "B",
"children": [
{
"id": "B1"
},
{
"id": "B2"
}
]
},
{
"id": "C",
"children": [
{
"id": "C1"
},
{
"id": "C2"
}
]
}
],
"links": [
{
"source": "A",
"target": "B"
},
{
"source": "B",
"target": "C"
},
{
"source": "C",
"target": "A"
},
]
}
var outerLayout = d3.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-500))
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(200))
var outerLinks = outerLinkContainer.selectAll(".link")
.data(outerGraph.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
.style("opacity", 0.2)
var outerNodes = outerNodeContainer.selectAll("g.outer")
.data(outerGraph.nodes, function (d) { return d.id; })
.join("circle")
.attr("class", "outer")
.style("fill", "pink")
.style("stroke", "blue")
.attr("r", 40)
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
outerLayout
.nodes(outerGraph.nodes)
.on("tick", ticked)
outerLayout
.force("link")
.links(outerGraph.links)
//###############################################
//############## inner force Layout #############
//###############################################
var innerGraph = {
"nodes": [
{ "id": "A1" },
{ "id": "A2" }
],
"links": [
{
"source": "A1",
"target": "A2"
}
]
}
var innerlayout = d3.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-500))
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(200))
var innerLinks = innerLinkContainer.selectAll(".link")
.data(innerGraph.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
var innerNodes = innerNodeContainer.selectAll("g.inner")
.data(innerGraph.nodes, function (d) { return d.id; })
.join("circle")
.style("fill", "orange")
.style("stroke", "blue")
.attr("r", 6)
.attr("class", "inner")
.attr("id", function (d) { return d.id; })
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
innerlayout
.nodes(innerGraph.nodes)
.on("tick", ticked)
innerlayout
.force("link")
.links(innerGraph.links)
//###############################################
//################## functons ###################
//###############################################
function ticked() {
outerLinks
.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;
});
innerLinks
.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;
});
outerNodes.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
innerNodes.attr("transform", function (d) {
return "translate(" + (d.x) + "," + (d.y) + ")";
});
}
function dragStarted(event, d) {
if (!event.active)
outerLayout.alphaTarget(0.3).restart();
innerlayout.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active)
outerLayout.alphaTarget(0)
innerlayout.alphaTarget(0)
d.fx = undefined;
d.fy = undefined;
}
</script>
</body>
</html>
I will update the post as soon as I found a solution.
Here's a slightly hack way to do it - I am a bit disappointed in the outcome because if you play with the outerNodes then the links between innerNodes cross over in an unattractive way.
The changes I made in your code:
update innerGraph so nodes have a parent property (plus add the links required to match your screenshot in the question)
add an additional class on outerNodes so that each outer node can be identified e.g. .outer_A, .outer_B etc
add an additional class on innerNodes so that each inner node can be identified e.g. .child_A1, .child_A2 etc
in ticked - for innerNodes return a point for the inner node so that it is sitting inside centre of it's parent at roughly 20px from the centre on the vector between the original force simulation selected point and the parent's centre.
in ticked - for innerLinks, force the source and target coordinates to update per the previous step
Those last two points are per here and here.
So it works - but only just. Vertical scrolling in the stack snippet seems to upset it a bit but it's maybe better if you try it out on your own dev environment. I still think you could you look at other tools - maybe this one from cytoscape.js and also the webcola example I mentioned in the comments?
var svg = d3.select("body").append("svg")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight);
var width = window.innerWidth
var height = window.innerHeight
var outerLinkContainer = svg.append("g").attr("class", "outerLinkContainer")
var outerNodeContainer = svg.append("g").attr("class", "outerNodeContainer")
var innerLinkContainer = svg.append("g").attr("class", "innerLinkContainer")
var innerNodeContainer = svg.append("g").attr("class", "innerNodeContainer")
//###############################################
//############# outer force Layouts #############
//###############################################
var outerGraph = {
"nodes": [
{
"id": "A",
"children": [
{
"id": "A1"
},
{
"id": "A2"
}
]
},
{
"id": "B",
"children": [
{
"id": "B1"
},
{
"id": "B2"
}
]
},
{
"id": "C",
"children": [
{
"id": "C1"
},
{
"id": "C2"
}
]
}
],
"links": [
{
"source": "A",
"target": "B"
},
{
"source": "B",
"target": "C"
},
{
"source": "C",
"target": "A"
},
]
}
var outerLayout = d3.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-500))
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(200))
var outerLinks = outerLinkContainer.selectAll(".link")
.data(outerGraph.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
.style("opacity", 0.2)
var outerNodes = outerNodeContainer.selectAll("g.outer")
.data(outerGraph.nodes, function (d) { return d.id; })
.join("circle")
.attr("class", d => `outer outer_${d.id}`)
.style("fill", "pink")
.style("stroke", "blue")
.attr("r", 40)
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
outerLayout
.nodes(outerGraph.nodes)
.on("tick", ticked)
outerLayout
.force("link")
.links(outerGraph.links)
//###############################################
//############## inner force Layout #############
//###############################################
var innerGraph = {
"nodes": [
{ "id": "A1", "parent": "A" },
{ "id": "A2", "parent": "A" },
{ "id": "B1", "parent": "B" },
{ "id": "B2", "parent": "B" },
{ "id": "C1", "parent": "C" },
{ "id": "C2", "parent": "C" }
],
"links": [
{
"source": "A1",
"target": "A2"
},
{
"source": "A2",
"target": "B2"
},
{
"source": "A1",
"target": "C2"
},
{
"source": "B1",
"target": "B2"
},
{
"source": "B1",
"target": "C1"
},
{
"source": "C2",
"target": "C1"
}
]
}
var innerlayout = d3.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-500))
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(200))
var innerLinks = innerLinkContainer.selectAll(".link")
.data(innerGraph.links)
.join("line")
.attr("class", "link linkChild")
.style("stroke", "black")
var innerNodes = innerNodeContainer.selectAll("g.inner")
.data(innerGraph.nodes, function (d) { return d.id; })
.join("circle")
.style("fill", "orange")
.style("stroke", "blue")
.attr("r", 6)
.attr("class", d => `inner child_${d.id}`)
.attr("id", function (d) { return d.id; })
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
innerlayout
.nodes(innerGraph.nodes)
.on("tick", ticked)
innerlayout
.force("link")
.links(innerGraph.links)
//###############################################
//################## functons ###################
//###############################################
function ticked() {
outerLinks
.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;
});
outerNodes.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
innerNodes.attr("transform", function (d) {
var parent = d3.select(`.outer_${d.parent}`);
var pr = parent.node().getBoundingClientRect();
var prx = pr.left + (pr.width / 2);
var pry = pr.top + (pr.height / 2);
var distance = Math.sqrt( ((d.x - prx) ** 2) + ((d.y - pry) ** 2 ));
var ratio = 20 / distance;
var childX = ((1 - ratio) * prx) + (ratio * d.x);
var childY = ((1 - ratio) * pry) + (ratio * d.y);
return "translate(" + (childX) + "," + (childY) + ")";
});
innerLinks.attr("x1", d => {
var m1 = d3.select(`.child_${d.source.id}`).node().transform.baseVal[0].matrix;
return m1.e;
}).attr("y1", d => {
var m1 = d3.select(`.child_${d.source.id}`).node().transform.baseVal[0].matrix;
return m1.f;
}).attr("x2", d => {
var m2 = d3.select(`.child_${d.target.id}`).node().transform.baseVal[0].matrix;
return m2.e;
}).attr("y2", d => {
var m2 = d3.select(`.child_${d.target.id}`).node().transform.baseVal[0].matrix;
return m2.f;
});
}
function dragStarted(event, d) {
if (!event.active)
outerLayout.alphaTarget(0.3).restart();
innerlayout.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active)
outerLayout.alphaTarget(0)
innerlayout.alphaTarget(0)
d.fx = undefined;
d.fy = undefined;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.5.0/d3.min.js"></script>

Dynamically resize a div when it is used as a node in a d3 force-directed graph

Hi Stackoverflow Community!
So my problem is the following:
I have a d3 force-directed graph. The nodes of this graph are divs.
Now i want to be able to resize this divs using the mouse. I use the jqueryui resizable() for this purpose.
Unfortunately it renders the ui changes (when i reach the edges of the nodea i get the mousesymbol for resize) but i cannot resize them. I believe it is because the d3-forcegraph overlays the jqueryui function. I tried to stop the graph so that i can access the resize functionality but this seems not to work.
Does anybody have an idea how i could make these divs resizable?
I created a fiddle to show what i mean: https://jsfiddle.net/5jgrf5h8/
//constants for the network visualisation
var height = window.innerHeight-20; //fullsize svg
var width = window.innerWidth-20;
var nodes = [
{"id": "RootNode", "group": 0},
{"id": "Node1",
"ip_adresses": [
{"ip_adress": "aa.bbb.114.80/28"}
],
"group": 1},
{"id": "Node2",
"ip_adresses": [
{"ip_adress": "aa.bbb.117.96/28"}
],
"group": 1},
{"id": "Node3",
"ip_adresses": [
{"ip_adress": "eeee:ffff:400:3001::7"},
{"ip_adress": "eeee:ffff:400:3001::8"},
{"ip_adress": "eeee:ffff:400:3001::9"},
{"ip_adress": "eeee:ffff:400:3001::10"},
{"ip_adress": "eeee:ffff:400:3001::11"},
{"ip_adress": "eeee:ffff:400:3001::12"},
{"ip_adress": "eeee:ffff:400:3001::13"},
{"ip_adress": "eeee:ffff:400:3001::14"},
{"ip_adress": "eeee:ffff:400:3001::15"},
{"ip_adress": "eeee:ffff:400:3001::16"},
{"ip_adress": "eeee:ffff:400:3001::17"}
],
"group": 1},
{"id": "Node4",
"ip_adresses": [
{"ip_adress": "cc.dd38.151"},
{"ip_adress": "cc.dd38.152"}
],
"group": 1},
{"id": "Node5",
"ip_adresses": [
{"ip_adress": "aa.bbb.114.36"},
{"ip_adress": "aa.bbb.114.37"}
],
"group": 1}
];
var links = [
{"source": "RootNode", "target": "Node1", "value": 140},
{"source": "RootNode", "target": "Node2", "value": 140},
{"source": "RootNode", "target": "Node3", "value": 140},
{"source": "RootNode", "target": "Node4", "value": 140},
{"source": "RootNode", "target": "Node5", "value": 140}
];
var color = d3.scaleOrdinal(d3.schemeCategory10);
//creating and configuring the d3 force-directed graph simulation
var simulation = d3.forceSimulation()
.force("charge", d3.forceManyBody().strength(-2000))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("link", d3.forceLink()
.distance(function(d){return d.value;})
.strength(1.3).id(function(d){return d.id;}));
//create a svg in the body of index.html
var svg = d3.select("body").append("svg")
.classed("simulation", 1)
.attr("width", width)
.attr("height", height);
//setting the links
var glink = svg.append("g")
.attr("class", "links")
.selectAll("links")
.data(links).enter();
var link = glink.append("polyline")
.attr("stroke-width", 2);
//setting the nodes. they will be div elements
var node = d3.select("body")
.append("div")
.attr("class", "nodes")
.selectAll(".node")
.data(nodes)
.enter().append("div")
.attr("class", "node")
.attr("id", function(d){return d.id;})
.style("background", function(d) { return color(d.group); });
node.each(function (d, i){
if(d.group === 1 )
{
console.log(d.id, i, d3.select(this).attr("id"));
d3.select(this).append("select")
.attr("size", 2)
.selectAll('option')
.data(nodes[i].ip_adresses)
.enter()
.append("option")
.text(function(d){return d.ip_adress;});
}
else
{
d3.select(this).text(function (d){return d.id;});
}
});
//start the simulation
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
//assigning data to the simulation and set tick
simulation
.nodes(nodes)
.on("tick", ticked);
simulation.force("link")
.links(links);
function ticked() {
link.attr("points", function(d) {
var sourceX = d.source.x;
var sourceY = d.source.y;
var targetX = d.target.x;
var targetY = d.target.y;
return sourceX + "," + sourceY + " " +
(sourceX + targetX)/2 + "," + (sourceY + targetY)/2 + " " +
targetX + ", " + targetY;
});
node.style('left', function(d ,i) {
return d.x-$("[id='"+d.id+"']").outerWidth()/2+"px";
})
.style('top', function(d) {
return d.y-$("[id='"+d.id+"']").outerHeight()/2+"px";
});
}
d3.select("body").selectAll("option").on("dblclick", function(){
alert(this.text);
});
//this would be nice but it is not working in the d3 graph
$( ".node" ).resizable();
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
d.fixed = true;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
First change, you need is remove the drag start and end events:
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
Reason: they are eating up the event so JQUERY resizable will not work.
Second add class to your select so that they are unique (will be used for jquery selection):
d3.select(this).append("select")
.attr("size", 2)
.attr("class", function(){ return "my-list" + i;})//giving unique class to all select.
Third add resize (alsoResize) to select unique class names made above, so that on resize the select also resizes.
$( ".node" ).each(function(i){
$( this ).resizable({alsoResize: ".my-list"+i});
})
working code here

How to draw the force chart in d3 v4?

I'm new to d3 and when I learnt how to draw a force chart, I had some problems about it. And at first, let we see my code here:
<html>
<head>
<meta charset="UTF-8">
<title>the force chart</title>
</head>
 
<body>
<script src="http://d3js.org/d3.v4.min.js"></script>
    <script>
var width = 400;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
var nodes = [ { "id": "English" },{ "id": "Italy" },
{ "id": "America" },{ "id": "Canada" },
{ "id": "Australia" },{ "id": "Japan" },
{ "id": "China" } ];
var edges = [ { "source": 0 , "target": 1 } , { "source": 0 , "target": 2 },
{ "source": 0 , "target": 3 } , { "source": 1 , "target": 4 },
{ "source": 1 , "target": 5 } , { "source": 1 , "target": 6 }, ];
var force = d3.forceSimulation(nodes)
.force("link",d3.forceLink()
.id(function(d){return d.id})
.distance(function(d){return 150}).strength([-400]))
.force("charge",d3.forceManyBody())
.force("center",d3.forceCenter(width , height));
force.restart(); //start
var svg_edges = svg.selectAll("line")
.data(edges)
.enter()
.append("line")
.style("stroke","#ccc")
.style("stroke-width",1);
var color = d3.scaleOrdinal(d3.schemeCategory20);
//add nodes
var svg_nodes = svg.selectAll("circle")
.data(nodes)
.enter()
.append("r",20)
.style("fill",function(d,i){
return color(i);
})
.call(d3.drag()); //to drag the nodes
//add information
var svg_texts = svg.selectAll("text")
.data(nodes)
.enter()
.append("text")
.style("fill", "black")
.attr("dx", 20)
.attr("dy", 8)
.text(function(d){
return d.id;
});
force.on("tick", function(){ //update the position of lines
svg_edges.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; })
//update the position of nodes
svg_nodes.attr("cx",function(d){ return d.x; })
.attr("cy",function(d){ return d.y; });
//update the position of information
svg_texts.attr("x",function(d){ return d.x; })
.attr("y",function(d){ return d.y; });
});
    </script>    
</body>
</html>
I want to draw a picture like this:
But my code can only show one node, just like this:
So I feel confused, because there is no error in Developer Tools. As I layout the force, I infer to https://github.com/d3/d3-force/blob/master/README.md#links . So I solve the problem which is result from the different versions. But why it still doesn't work? Could you help me? I'm very appreciate it if you help me! Thank you!
Besides the points already explained by #Vinod:
.append("circle")
and
.force("center", d3.forceCenter(width/2, height/2));
You have a trailing comma. But the most important is this, to show the edges:
First, add the edges to the simulation:
force.force("link")
.links(edges);
And then, change the links id. Right now, there is no property called id. So, it should be:
.force("link", d3.forceLink()
.id(function(d,i) {
return i
})
//the rest of the function
Here is a demo:
var width = 400;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var nodes = [{
"id": "English"
}, {
"id": "Italy"
}, {
"id": "America"
}, {
"id": "Canada"
}, {
"id": "Australia"
}, {
"id": "Japan"
}, {
"id": "China"
}];
var edges = [{
"source": 0,
"target": 1
}, {
"source": 0,
"target": 2
}, {
"source": 0,
"target": 3
}, {
"source": 1,
"target": 4
}, {
"source": 1,
"target": 5
}, {
"source": 1,
"target": 6
}];
var force = d3.forceSimulation()
.force("link", d3.forceLink()
.id(function(d,i) {
return i
})
.distance(function(d) {
return 150
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width/2, height/2));
force.restart(); //start
var svg_edges = svg.selectAll("line")
.data(edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
var color = d3.scaleOrdinal(d3.schemeCategory20);
//add nodes
var svg_nodes = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", 20)
.style("fill", function(d, i) {
return color(i);
})
.call(d3.drag()); //to drag the nodes
//add information
var svg_texts = svg.selectAll("text")
.data(nodes)
.enter()
.append("text")
.style("fill", "black")
.attr("dx", 20)
.attr("dy", 8)
.text(function(d) {
return d.id;
});
force.nodes(nodes);
force.force("link")
.links(edges);
force.on("tick", function() { //update the position of lines
svg_edges.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;
})
//update the position of nodes
svg_nodes.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
//update the position of information
svg_texts.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
});
<script src="https://d3js.org/d3.v4.min.js"></script>
There are several mistakes in your code
firstly you need to append circle like this
var svg_nodes = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r",20)
.style("fill",function(d,i){
return color(i);
})
.call(d3.drag()); //to drag the nodes
where as your code append r which is no SVG tag moreover the center of the graph should not be width and height it should be width/2 and height/2 to make it at center
See this fiddle http://jsfiddle.net/Qh9X5/9515/
similarly for lines you are using edges data which don't have the x,y values you need to pass the xy value for drawing lines
For a complete solution note in v3.3 See this https://jsfiddle.net/3uehrfj8/1/ with all nodes and edges

Unable to get d3.js data.join and key function working when selecting line

This is what I'm currently using.
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Git commit history</title>
</head>
<body>
<button onclick="f(data0)">Original</button>
<button onclick="f(data1)">Final</button>
<div id="chart"></div>
<script src='http://d3js.org/d3.v3.min.js'></script>
<script>
var data1 = {"directed": true, "HEAD": "37e1d1e19f1ed57f8635ba4ba48d7a6a16ec52f6", "links": [{"source": 0, "target": 2}, {"source": 2, "target": 1}, {"source": 3, "target": 4}, {"source": 4, "target": 0}, {"source": 4, "target": 5}, {"source": 5, "target": 2}], "multigraph": false, "graph": [], "labels": ["master"], "master": "37e1d1e19f1ed57f8635ba4ba48d7a6a16ec52f6", "nodes": [{"message": "Add barn door", "id": "2b818acb7782772d0b43a0fbfd18320348c66d09", "pos": [182.04, 162.0]}, {"message": "Initial commit", "id": "5d49116ea5679a9eb21225f05dd4874b3a0b5e35", "pos": [371.04, 18.0]}, {"message": "Add animals", "id": "a21fd23c9a9742c93febff279d7f917d457c0f04", "pos": [371.04, 90.0]}, {"message": "Add chickens", "id": "37e1d1e19f1ed57f8635ba4ba48d7a6a16ec52f6", "pos": [371.04, 306.0]}, {"message": "Merge branch 'add-barn-doors'", "id": "f0f204010fe90e377f37c8e466110e49e420ac9e", "pos": [371.04, 234.0]}, {"message": "Remove cow", "id": "009d60bee58372a19e1188368ecfcc3c9ed5c2f1", "pos": [560.04, 162.0]}]}
var data0 = {"directed": true, "HEAD": "f0f204010fe90e377f37c8e466110e49e420ac9e", "links": [{"source": 0, "target": 4}, {"source": 1, "target": 0}, {"source": 1, "target": 2}, {"source": 2, "target": 4}, {"source": 4, "target": 3}], "multigraph": false, "graph": [], "labels": ["master"], "master": "f0f204010fe90e377f37c8e466110e49e420ac9e", "nodes": [{"message": "Add barn door", "id": "2b818acb7782772d0b43a0fbfd18320348c66d09", "pos": [182.04, 162.0]}, {"message": "Merge branch 'add-barn-doors'", "id": "f0f204010fe90e377f37c8e466110e49e420ac9e", "pos": [371.04, 234.0]}, {"message": "Remove cow", "id": "009d60bee58372a19e1188368ecfcc3c9ed5c2f1", "pos": [560.04, 162.0]}, {"message": "Initial commit", "id": "5d49116ea5679a9eb21225f05dd4874b3a0b5e35", "pos": [371.04, 18.0]}, {"message": "Add animals", "id": "a21fd23c9a9742c93febff279d7f917d457c0f04", "pos": [371.04, 90.0]}]}
var w = 1500,
H = 50,
fill = d3.scale.category20();
var h = H;
var vis = d3.select("#chart")
.append("svg:svg");
var f = function(data) {
h = H*1.45*data.nodes.length;
vis.attr("height", h)
.attr("width", w);
h = H*1.45*data.nodes.length;
vis.attr("height", h);
var transitiontime = 150;
var PosX = function(d, i, location) { return data.nodes[d[location]].pos[0]; };
var PosY = function(d, i, location) { return h-data.nodes[d[location]].pos[1]; };
var reverseMap = {};
for(var i=0; i<data.nodes.length; i++){
var p = data.nodes[i];
var hash = p.id;
reverseMap[hash] = p;
};
function poslink(d, i){ try{ return data.nodes[d.source].id + "" + data.nodes[d.target].id;} catch(err) {console.log(err); } }
var linkUpdateSelection = vis.selectAll(".link")
.data(data.links, poslink);
linkUpdateSelection.exit().remove();
linkUpdateSelection
.enter().append("line") // attach a line
.attr("class", "link")
.style("stroke", "black") // colour the line
.attr("x1", function(d, i) {return PosX(d, i, "source");}) // x position of the first end of the line
.attr("y1", function(d, i) {return PosY(d, i, "source");}) // y position of the first end of the line
.attr("x2", function(d, i) {return PosX(d, i, "target");}) // x position of the second end of the line
.attr("y2", function(d, i) {return PosY(d, i, "target");}) // y position of the second end of the line
.style('opacity', 0);
linkUpdateSelection
.attr("class", "link")
.style("stroke", "black") // colour the line
.transition()
.delay(function(d,i) {return i*transitiontime})
.attr("x1", function(d, i) {return PosX(d, i, "source");}) // x position of the first end of the line
.attr("y1", function(d, i) {return PosY(d, i, "source");}) // y position of the first end of the line
.attr("x2", function(d, i) {return PosX(d, i, "target");}) // x position of the second end of the line
.attr("y2", function(d, i) {return PosY(d, i, "target");}) // y position of the second end of the line
.style('opacity', 1);
var nodeUpdateSelection = vis.selectAll("circle.node")
.data(data.nodes, function(d) {return d.id});
nodeUpdateSelection.exit().remove();
nodeUpdateSelection.enter().append("svg:circle")
.attr("class", "node")
.attr("cx", function(d, i) { return d.pos[0]; })
.attr("cy", function(d, i) { return h-d.pos[1]; })
.attr("r", 0)
.attr('fill', function(d, i) {if (reverseMap[d.id]==reverseMap[data['HEAD']]){return '#99FF66';} else {return 'red';}} )
.on('mouseover', function(d,i) {
d3.select(this).transition()
.ease('cubic-out')
.duration('200')
.attr("r", 15)
})
.on('mouseout', function(d,i) {
d3.select(this).transition()
.ease('cubic-out')
.duration('200')
.attr("r", 10)
});
nodeUpdateSelection.transition()
.delay(function(d,i) {return i*transitiontime})
.attr("class", "node")
.attr("cx", function(d, i) { return d.pos[0]; })
.attr("cy", function(d, i) { return h-d.pos[1]; })
.attr('fill', function(d, i) {if (reverseMap[d.id]==reverseMap[data['HEAD']]){return '#99FF66';} else {return 'red';}} )
.attr("r", 10);
var textUpdateSelection = vis.selectAll("text.message")
.data(data.nodes, function(d) {return d.id});
textUpdateSelection.exit().remove();
textUpdateSelection
.enter().append("text")
.text(function(d) { return d.id.substring(0, 6) + " - " + d.message; })
.attr("x", function(d) { return 15+d.pos[0]; })
.attr("y", function(d) { return h-d.pos[1]+5; })
.attr("font-family", "sans-serif")
.attr("class", "message")
.attr("font-size", "15px")
.attr("fill", "blue")
.style('fill-opacity', 0);
textUpdateSelection
.text(function(d) { return d.id.substring(0, 6) + " - " + d.message; })
.transition()
.delay(function(d,i) {return i*transitiontime})
.style('fill-opacity', 1)
.attr("x", function(d) { return 15+d.pos[0]; })
.attr("y", function(d) { return h-d.pos[1]+5; })
.attr("class", "message")
.attr("font-family", "sans-serif")
.attr("font-size", "15px")
.attr("fill", "blue");
var labelUpdateSelection = vis.selectAll("text.labels")
.data(data.labels);
var labelPosX = function(d) { return reverseMap[d].pos[0]; };
var labelPosY = function(d) { return reverseMap[d].pos[1]; };
labelUpdateSelection.exit().remove();
labelUpdateSelection.enter().append("text")
.text(function(d) { return d })
.style('fill-opacity', 0)
.attr("x", function(d, i) { return data[d][0]- 75; })
.attr("y", function(d, i) { return h-data[d][1] + 5; })
.attr("class", "labels")
.attr("font-family", "sans-serif")
.attr("font-size", "15px");
labelUpdateSelection
.text(function(d) { return d })
.transition()
.delay(function(d,i) {return i*transitiontime})
.style('fill-opacity', 1)
.attr("x", function(d, i) { return labelPosX(data[d]) - 50 ; })
.attr("y", function(d, i) { return h-labelPosY(data[d]) - 25 + i*5; })
.attr("class", "labels")
.attr("font-family", "sans-serif")
.attr("font-size", "15px");
}
f(data0)
</script>
</body>
</html>
However, during an update, the lines do not move as expected. I'll try posting a screencast of what is happening to explain better. My current implementation works for nodes in a graph, but not for the lines for some reason. I feel like I'm missing something silly here.
Full code over here
Edit :
Here is a screencast of what currently occurs. I want the lines between two nodes to stay between those two nodes.
Edit II :
I've added a full working example above, with the data included as part of the code
The problem is that your key function is referencing data, which changes between calls. The source and target nodes of a link are referred to by index, and the same index points to different nodes with different data. That is, for the original data, index 0 may refer to "foo", but with the new data the same index refers to "bar".
The easiest way to fix this is to save the index with the links in a way that doesn't change when the data is changed:
data.links.forEach(function(d) {
d.id = data.nodes[d.source].id + "" + data.nodes[d.target].id;
});
Then your data matching becomes
var linkUpdateSelection = vis.selectAll("line.link")
.data(data.links, function(d) { return d.id; });
and the update works as expected. Complete demo here.

d3's force layout not aligning nodes and links properly

I am building a graph visualization using D3 library. But it is giving some problems.
There can be multiple boxes and each box can have multiple nodes inside it. There are links between various links.
Nodes should be draggable and links should also drag with their respective links.
When i am applying force layout, the nodes are created outside their respective boxes and their respective links.
Any help would be greatly appreciated.
Here's my jsfiddle link : http://jsfiddle.net/qfhfzrL2/
var width = 700,
height = 600;
radius =10;
var svg = d3.select("body").append("svg")
.attr("width", 900)
.attr("height", 800)
.style("background-color","#ECE9E9")
.attr("overflow","scroll");
var graph = {
"nodes": [
{"name": "Probe1", "group": "A"},
{"name": "Probe2", "group": "A"},
{"name": "Probe3", "group": "A"},
{"name": "Probe4", "group": "B"},
{"name": "Probe5", "group": "B"},
{"name": "Probe6", "group": "B"},
{"name": "Probe7", "group": "C"}
],
"links": [
{"source": 1, "target": 0, "value": 1},
{"source": 2, "target": 0, "value": 8},
{"source": 3, "target": 0, "value": 10},
{"source": 3, "target": 2, "value": 6},
{"source": 4, "target": 0, "value": 1}
]
};
var outerData = [
{"group": "A"},
{"group": "B"},
{"group": "C"},
{"group": "D"}
];
var outerLayout = d3.layout.force()
.size([800,800])
.charge(-8000)
.gravity(0.1)
.friction(.7)
.links([])
.nodes(outerData)
.on("tick", outerTick)
.start();
var outerNodes = svg.selectAll("g.outer")
.data(outerData//, function (d) {return d.group;}
)
.enter()
.append("g")
.attr("class", "outer")
.attr("id", function (d) {return d.group;})
.call(outerLayout.drag());
outerNodes
.append("rect")
.style("fill", "#ECE9E9")
.style("stroke", "black")
.attr("height",150)
.attr("width",150);
var oNode = svg.selectAll(".outer")
//.data(graph.nodes)
.append("title")
.text(function(d) { return d.group; });
//---------functions
function outerTick (e) {outerNodes.attr("transform", function (d) {return "translate(" + d.x + "," + d.y + ")";});
//outerNodes.attr("x", function(d) { return d.x = Math.max(20, Math.min(width, d.x)); })
// .attr("y", function(d) { return d.y = Math.max(20, Math.min(height, d.y)); });
}
function changeForceouter(charge, gravity) {
var tmp = "inner"+"A"+"Layout";
outerLayout .charge(charge).gravity(gravity);
}
setTimeout(function(){ changeForceouter(0,0); }, 2000);
//---------------------------
for(var i=0;i<7;i++)
{
var ident = "#" + graph.nodes[i].group;
svg.select(ident).append("circle")
.attr("r", radius - 2)
.style("fill", "#8DD623" );
}
var force = d3.layout.force()
.gravity(.4)
.charge(-300)
.friction(.7)
.linkDistance(1000)
.linkStrength(0)
.size([width, height]);
var link = svg.selectAll("line")
.data(graph.links)
.enter().append("line")
.style("fill", "green" );
var node = svg.selectAll("circle")
.data(graph.nodes)
//.call(force.drag);
.call(force.drag()
.on("dragstart", function(){
d3.event.sourceEvent.stopPropagation();
})
);
node.append("title")
.text(function(d) { return d.name; });
force
.nodes(graph.nodes)
.links(graph.links)
.on("tick", tick)
.start();
//--------------------------
/*while (force.alpha() >0.005) {
force.tick();
}*/
function changeForce(charge, gravity) {
force.charge(charge).gravity(gravity);
}
//----------------------------
function tick() {
node.attr("cx", function(d) { return d.x = Math.max(radius, Math.min(width - radius, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(radius, Math.min(height - radius, d.y)); });
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; });
}
//---------------------------
/* var populateCell = function(i) {
var ident = "#" + graph.nodes[i].group;
d3.select(ident).append("circle")
.attr("r", radius - .75)
.style("fill", "#8DD623" );
}; */
setTimeout(function(){ changeForce(0, 0);}, 2000);
//changeForce(0, 0);

Categories