Release all sticky nodes - d3.js

How can I modify the following example so that all sticky nodes are released when a button is pressed? I've seen this implemented for nodes to be released (unstuck) with a doubleclick (here), but I want to release them all at the same time (my plan is to include this in a restart() function for the graph).
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
</style>
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
//create somewhere to put the force directed graph
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var nodes_data = [{
"name": "Travis",
"sex": "M"
},
{
"name": "Rake",
"sex": "M"
},
{
"name": "Diana",
"sex": "F"
},
{
"name": "Rachel",
"sex": "F"
},
{
"name": "Shawn",
"sex": "M"
},
{
"name": "Emerald",
"sex": "F"
}
]
var links_data = [{
"source": "Travis",
"target": "Rake"
},
{
"source": "Diana",
"target": "Rake"
},
{
"source": "Diana",
"target": "Rachel"
},
{
"source": "Rachel",
"target": "Rake"
},
{
"source": "Rachel",
"target": "Shawn"
},
{
"source": "Emerald",
"target": "Rachel"
}
]
//set up the simulation
var simulation = d3.forceSimulation()
//add nodes
.nodes(nodes_data);
//add forces
//we're going to add a charge to each node
//also going to add a centering force
//and a link force
var link_force = d3.forceLink(links_data)
.id(function(d) {
return d.name;
});
simulation
.force("charge_force", d3.forceManyBody())
.force("center_force", d3.forceCenter(width / 2, height / 2))
.force("links", link_force);
//add tick instructions:
simulation.on("tick", tickActions);
//draw circles for the links
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes_data)
.enter()
.append("circle")
.attr("r", 10)
.attr("fill", "red");
//draw lines for the links
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links_data)
.enter().append("line")
.attr("stroke-width", 2);
var drag_handler = d3.drag()
.on("start", drag_start)
.on("drag", drag_drag)
.on("end", drag_end);
//same as using .call on the node variable as in https://bl.ocks.org/mbostock/4062045
drag_handler(node)
//drag handler
//d is the node
function drag_start(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function drag_drag(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function drag_end(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = d.x;
d.fy = d.y;
}
function tickActions() {
//update circle positions each tick of the simulation
node
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
//update link positions
//simply tells one end of the line to follow one node around
//and the other end of the line to follow the other node around
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;
});
}
</script>

That's not a Bostock's bl.ocks, and there is no double click there to release the nodes.
Anyway, you just need to remove the fx and fy properties, and reheat the simulation again:
d3.select("button").on("click", function() {
node.each(function(d) {
d.fx = d.fy = null;
})
simulation.alphaTarget(0.3).restart();
})
I'm using alphaTarget because that's what you have in your code, but you should consider using alpha instead.
Here is the code with that change:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
</style>
<button>Release</button>
<svg width="600" height="400"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
//create somewhere to put the force directed graph
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var nodes_data = [{
"name": "Travis",
"sex": "M"
},
{
"name": "Rake",
"sex": "M"
},
{
"name": "Diana",
"sex": "F"
},
{
"name": "Rachel",
"sex": "F"
},
{
"name": "Shawn",
"sex": "M"
},
{
"name": "Emerald",
"sex": "F"
}
]
var links_data = [{
"source": "Travis",
"target": "Rake"
},
{
"source": "Diana",
"target": "Rake"
},
{
"source": "Diana",
"target": "Rachel"
},
{
"source": "Rachel",
"target": "Rake"
},
{
"source": "Rachel",
"target": "Shawn"
},
{
"source": "Emerald",
"target": "Rachel"
}
]
//set up the simulation
var simulation = d3.forceSimulation()
//add nodes
.nodes(nodes_data);
//add forces
//we're going to add a charge to each node
//also going to add a centering force
//and a link force
var link_force = d3.forceLink(links_data)
.id(function(d) {
return d.name;
});
simulation
.force("charge_force", d3.forceManyBody())
.force("center_force", d3.forceCenter(width / 2, height / 2))
.force("links", link_force);
//add tick instructions:
simulation.on("tick", tickActions);
//draw circles for the links
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes_data)
.enter()
.append("circle")
.attr("r", 10)
.attr("fill", "red");
//draw lines for the links
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links_data)
.enter().append("line")
.attr("stroke-width", 2);
var drag_handler = d3.drag()
.on("start", drag_start)
.on("drag", drag_drag)
.on("end", drag_end);
//same as using .call on the node variable as in https://bl.ocks.org/mbostock/4062045
drag_handler(node)
//drag handler
//d is the node
function drag_start(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function drag_drag(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function drag_end(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = d.x;
d.fy = d.y;
}
function tickActions() {
//update circle positions each tick of the simulation
node
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
//update link positions
//simply tells one end of the line to follow one node around
//and the other end of the line to follow the other node around
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;
});
}
d3.select("button").on("click", function() {
node.each(function(d) {
d.fx = d.fy = null;
})
simulation.alphaTarget(0.3).restart();
})
</script>

Related

D3.js various link distance

I am in need to implement a various link distance. I added a function call during the initialization of the force / simulation. Still, it looks like the settings are ignored. I want to add another node very close, that barely a hair would fit between. Is this even possible?
I noticed a similar question on stackoverflow but this requires another plugin, furthermore this question was created 4 years ago. Maybe vanilla d3.js already added the feature.
var graph = {
"nodes": [
{
"id": 0
},
{
"id": 1
},
{
"id": 2
},
{
"id": 3
}
],
"links": [
{
"source": 0,
"target": 1,
"distance": 0.1
},
{
"source": 0,
"target": 2,
"distance": 150
},
{
"source": 0,
"target": 3,
"distance": 20
}
]
}
var svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
var linksContainer = svg.append("g").attr("class", linksContainer)
var nodesContainer = svg.append("g").attr("class", nodesContainer)
var sourceNode;
var force = d3.forceSimulation()
//.force("link", d3.forceLink().id(function (d) { return d.id }).distance(80))
.force("link", d3.forceLink().distance(linkDistance).strength(0.1))
.force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2))
.force("collision", d3.forceCollide().radius(90))
function linkDistance(d) {
console.log(d.distance)
return d.distance;
}
initialize()
function initialize() {
link = linksContainer.selectAll(".link")
.data(graph.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
.style("stroke-width", 1)
node = nodesContainer.selectAll(".node")
.data(graph.nodes, d => d.id)
.join("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
.on("click", addNode)
node.selectAll("circle")
.data(d => [d])
.join("circle")
.attr("r", 30)
.style("fill", "whitesmoke")
node.append("text")
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle")
.attr("font-size", 15)
.attr("pointer-events", "none")
.text(function (d) {
return d.id
})
force
.nodes(graph.nodes)
.on("tick", ticked);
force
.force("link")
.links(graph.links)
}
function ticked() {
// update link positions
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;
});
// update node positions
node
.attr("transform", function (d) {
return "translate(" + d.x + ", " + d.y + ")";
});
}
function dragStarted(event, d) {
if (!event.active) force.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
PosX = d.x
PosY = d.y
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active) force.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
}
function addNode(event, d) {
const nodeId = graph.nodes.length
graph.nodes.push({id: nodeId})
graph.links.push({source: d, target: nodeId, distance: 1})
initialize()
}
body {
height: 100%;
background: #e6e7ee;
overflow: hidden;
margin: 0px;
}
.faded {][1]
opacity: 0.1;
transition: 0.3s opacity;
}
.highlight {
opacity: 1;
}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- d3.js framework -->
<script src="https://d3js.org/d3.v6.js"></script>
<!-- fontawesome stylesheet https://fontawesome.com/ -->
<script src="https://kit.fontawesome.com/39094309d6.js" crossorigin="anonymous"></script>
</head>
<style>
</style>
<body>
<svg id="svg"></svg>
</body>
The distance function is correctly set and specified.
Problem comes from the forceCollide, which is set to a radius of 90, and pushes the nodes apart regardless of specified distance.
Decreasing the radius of forceCollide fixes the problem, as illustrated below :)
var graph = {
"nodes": [
{
"id": 0
},
{
"id": 1
},
{
"id": 2
},
{
"id": 3
}
],
"links": [
{
"source": 0,
"target": 1,
"distance": 0.1
},
{
"source": 0,
"target": 2,
"distance": 150
},
{
"source": 0,
"target": 3,
"distance": 20
}
]
}
var svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
var linksContainer = svg.append("g").attr("class", linksContainer)
var nodesContainer = svg.append("g").attr("class", nodesContainer)
var sourceNode;
var force = d3.forceSimulation()
//.force("link", d3.forceLink().id(function (d) { return d.id }).distance(80))
.force("link", d3.forceLink().distance(linkDistance).strength(.1))
.force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2))
.force("collision", d3.forceCollide().radius(9))
function linkDistance(d) {
console.log('distance: ', d.distance)
return d.distance;
}
initialize()
function initialize() {
link = linksContainer.selectAll(".link")
.data(graph.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
.style("stroke-width", 1)
node = nodesContainer.selectAll(".node")
.data(graph.nodes, d => d.id)
.join("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
.on("click", addNode)
node.selectAll("circle")
.data(d => [d])
.join("circle")
.attr("r", 30)
.style("fill", "whitesmoke")
node.append("text")
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle")
.attr("font-size", 15)
.attr("pointer-events", "none")
.text(function (d) {
return d.id
})
force
.nodes(graph.nodes)
.on("tick", ticked);
force
.force("link")
.links(graph.links)
}
function ticked() {
// update link positions
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;
});
// update node positions
node
.attr("transform", function (d) {
return "translate(" + d.x + ", " + d.y + ")";
});
}
function dragStarted(event, d) {
if (!event.active) force.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
PosX = d.x
PosY = d.y
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active) force.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
}
function addNode(event, d) {
const nodeId = graph.nodes.length
graph.nodes.push({id: nodeId})
graph.links.push({source: d, target: nodeId, distance: 1})
initialize()
}
body {
height: 100%;
background: #e6e7ee;
overflow: hidden;
margin: 0px;
}
.faded {][1]
opacity: 0.1;
transition: 0.3s opacity;
}
.highlight {
opacity: 1;
}
<!-- d3.js framework -->
<script src="https://d3js.org/d3.v6.js"></script>
<!-- fontawesome stylesheet https://fontawesome.com/ -->
<script src="https://kit.fontawesome.com/39094309d6.js" crossorigin="anonymous"></script>
<svg id="svg"></svg>

d3.js - select force layout group not update the nodes

Below example should update the nodes after select the groups at the bottom. but the select works fine, the nodes on the svg not updated!
force_json()
function force_json(){
var svg = d3.select('body').append('svg')
.attr('width',200).attr('height',100)
.style('border','1px solid red'),
width = +svg.attr("width"),
height = +svg.attr("height");
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
var color = d3.scaleOrdinal(d3.schemeCategory10);
var jsonobj = {
"nodes": [
{"id": "Myriel", "group": 1},
{"id": "Napoleon", "group": 2},
{"id": "Mlle.Baptistine", "group": 3},
{"id": "Mme.Magloire", "group": 1},
],
"links": [
{"source": "Napoleon", "target": "Myriel", "value": 10},
{"source": "Mlle.Baptistine", "target": "Myriel", "value": 8},
{"source": "Mme.Magloire", "target": "Myriel", "value": 10},
]
}
// d3.json("miserables.json",function(error, graph) {
// if (error) throw error;
// });
process_data(jsonobj)
function process_data(graph) {
var currNodes = graph.nodes
var currLinks = graph.links
var nodesByGroup = d3.group(graph.nodes,d => d.group)
var catMenu = d3.select("body").append('div')
catMenu
.append("select")
.selectAll("option")
.data(nodesByGroup)
.enter()
.append("option")
.attr("value", function(d,i) {
return d[0];
})
.text(function(d,i){
return d[0];
})
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(currLinks)
.enter().append("line")
.attr('stroke','#aaa')
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(currNodes)
.enter().append("circle")
.attr("r", 5)
.attr('pointer-events','all')
.attr('stroke','none')
.attr('stroke-wdith',40)
.attr("fill", function(d) { return color(d.group);})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("title")
.text(function(d) { return d.id; });
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {return d.id;})
simulation
.nodes(currNodes)
.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("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
catMenu.on('change', function(){
var selectedGroup = +d3.select(this)
.select("select")
.property("value");
currNodes = filterNodes(selectedGroup);
});
function filterNodes(group) {
var filteredNodes = nodesByGroup.get(group)
return filteredNodes;
}
}
function dragstarted(event,d) {
if (!event.active) simulation.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) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
Re-render filtered nodes and links upon group selection:
force_json()
function force_json(){
var svg = d3.select('body').append('svg')
.attr('width',200).attr('height',200)
.style('border','1px solid red'),
width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory10);
var jsonobj = {
"nodes": [
{"id": "Myriel", "group": 1},
{"id": "Napoleon", "group": 2},
{"id": "Mlle.Baptistine", "group": 3},
{"id": "Mme.Magloire", "group": 1},
{"id": "A", "group": 2},
{"id": "B", "group": 3},
{"id": "C", "group": 3}
],
"links": [
{"source": "Napoleon", "target": "Myriel", "value": 10},
{"source": "Napoleon", "target": "A", "value": 10},
{"source": "Mlle.Baptistine", "target": "Myriel", "value": 8},
{"source": "Mlle.Baptistine", "target": "B", "value": 8},
{"source": "Mme.Magloire", "target": "Myriel", "value": 10},
{"source": "A", "target": "B", "value": 10},
{"source": "C", "target": "B", "value": 10},
]
}
process_data(jsonobj)
function process_data(graph) {
var nodesByGroup = d3.group(graph.nodes,d => d.group)
var catMenu = d3.select("body").append('div')
catMenu
.append("select")
.selectAll("option")
.data(nodesByGroup)
.enter()
.append("option")
.attr("value", function(d,i) {
return d[0];
})
.text(function(d,i){
return d[0];
})
catMenu.select('select').append('option').text('all').attr("selected", "selected");
const updateGraph = (nodes, links) => {
const simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
svg.selectAll('g').remove();
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr('stroke','#aaa')
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 5)
.attr('pointer-events','all')
.attr('stroke','none')
.attr('stroke-wdith',40)
.attr("fill", function(d) { return color(d.group);})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("title")
.text(d => d.id);
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {return d.id;})
simulation
.nodes(nodes)
.on("tick", ticked);
simulation.force("link")
.links(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("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
function dragstarted(event,d) {
if (!event.active) simulation.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) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}
updateGraph(graph.nodes, graph.links);
catMenu.on('change', function(){
var selectedGroup = d3.select(this)
.select("select")
.property("value");
let nodes, links;
if (selectedGroup === 'all') {
nodes = graph.nodes;
links = graph.links;
} else {
const group = parseInt(selectedGroup);
nodes = graph.nodes.filter(n => n.group === group);
console.log(graph.links)
links = graph.links.filter(link => {
const source = nodes.find(n => n.id === link.source.id);
const target = nodes.find(n => n.id === link.target.id);
return source && target;
})
}
updateGraph(nodes, links);
});
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>

D3 Adding text to Node SVG

I am trying to get the below code working with adding labels, still no luck, seems that my labels are written on top of the page instead of on the node itself. I think it is a binding issue with SVG.
I tried to add the title but I didn't manage to display it.
<!-- INITAL SETTINGS ------------------------------------------------------------------------------->
<!-------------------------------------------------------------------------------------------------->
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.links line {
stroke: #999;
stroke-opacity: 0.5;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.2px;
}
</style>
<!-- the SVG viewport will be 1100px by 900px -->
<svg width="1100" height="900" viewbox="0 0 1100 900">
<text> </text>
<!-- SVG content drawn onto the SVG canvas -->
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// DATA DEFINITIONS NODES & LINKS ------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
var graph = {
"nodes": [
{ "id": "xxx1", "group": 3 },
{ "id": "xxx2", "group": 1 },
{ "id": "xxx3", "group": 1 }
],
"links": [
{ "source": "xxx1", "target": "xxx2", "value": 42 },
{ "source": "xxx2", "target": "xxx3", "value": 6 },
{ "source": "xxx1", "target": "xxx3", "value": 13 }
]
};
// ---------------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------
// D3 CODE
// ---------------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------
// create the construct/canvas for drawing
var svg = d3.select("svg")
//set width/height
width = +svg.attr("width"),
height = +svg.attr("height")
preserveAspectRatio = "xMinYMin meet"
//color definition
var color = d3.scaleOrdinal(d3.schemeCategory20);
//Creates a new simulation with the specified array of nodes
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width/2.3, height/2.3))
// adding links
//The g element is a container element for grouping related graphics together
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); });
//adding nodes
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 6)
.attr("fill", function (d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
//var text = svg.append("g")
// .attr("class", "text")
// .selectAll("circle")
// .data(graph.nodes)
// .enter().append("id")
// .attr("cx", "12")
// .attr("cy", ".35em")
// .attr("text-anchor", "middle")
// .text(function(d) { return d.id });
// adding title
node.append("title")
.text(function (d) { return (d.id); });
var text = svg.append("g")
.attr("class", "labels")
.selectAll("text")
.data(graph.nodes)
.enter().append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.id });
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
// ticked functionality
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("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.call(force.drag);
}
//The data and the circle element’s position are updated during the drag event
// when dragged
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
// when dragged completed
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
// when dragged ended
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
You have to move the texts as well in the tick function:
text.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
Here is your code with that change:
var graph = {
"nodes": [{
"id": "xxx1",
"group": 3
}, {
"id": "xxx2",
"group": 1
}, {
"id": "xxx3",
"group": 1
}
],
"links": [{
"source": "xxx1",
"target": "xxx2",
"value": 42
}, {
"source": "xxx2",
"target": "xxx3",
"value": 6
}, {
"source": "xxx1",
"target": "xxx3",
"value": 13
}]
};
// create the construct/canvas for drawing
var svg = d3.select("svg")
//set width/height
width = +svg.attr("width"),
height = +svg.attr("height")
preserveAspectRatio = "xMinYMin meet"
//color definition
var color = d3.scaleOrdinal(d3.schemeCategory20);
//Creates a new simulation with the specified array of nodes
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2.3, height / 2.3))
// adding links
//The g element is a container element for grouping related graphics together
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);
});
//adding nodes
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 6)
.attr("fill", function(d) {
return color(d.group);
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// adding title
node.append("title")
.text(function(d) {
return (d.id);
});
var text = svg.append("g")
.attr("class", "labels")
.selectAll("text")
.data(graph.nodes)
.enter().append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.id
});
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
// ticked functionality
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("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
text.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
}
//The data and the circle element’s position are updated during the drag event
// when dragged
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
// when dragged completed
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
// when dragged ended
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
.links line {
stroke: #999;
stroke-opacity: 0.5;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.2px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="500" height="400" viewbox="0 0 500 400">
<text> </text>
</svg>

d3js force node xy start position

I have a node with fx/fy parameter on simulation starting. This node is fixed on the correct position. But now I want to define x and y coordinates for one or multiple other nodes (in this example: number 10 in jsfiddle), the purpose is to start the simulation of this "unfixed" nodes on a given position and not 0/0. Why is the node fixed on 0/0?
jsfiddle example: https://jsfiddle.net/6g9howo7/2/
var nodes = [
{
"id" : "1",
"fx" : "225",
"fy" : "225"
},
{
"id" : "2"
},
{
"id" : "3"
},
{
"id" : "4"
},
{
"id" : "5"
},
{
"id" : "6"
},
{
"id" : "7"
},
{
"id" : "8"
},
{
"id" : "9"
},
{
"id" : "10",
"x" : "125",
"y" : "125"
},
{
"id" : "11"
},
{
"id" : "12"
},
{
"id" : "13"
},
{
"id" : "14"
},
{
"id" : "15"
}
]
var links =
[
{
"source" : 1,
"target" : 2
},
{
"source" : 1,
"target" : 3
},
{
"source" : 1,
"target" : 4
},
{
"source" : 1,
"target" : 5
},
{
"source" : 1,
"target" : 6
},
{
"source" : 1,
"target" : 7
},
{
"source" : 1,
"target" : 8
},
{
"source" : 1,
"target" : 9
},
{
"source" : 1,
"target" : 10
},
{
"source" : 10,
"target" : 11
},
{
"source" : 10,
"target" : 12
},
{
"source" : 10,
"target" : 13
},
{
"source" : 10,
"target" : 14
},
{
"source" : 10,
"target" : 15
}
]
var svg = d3.select("svg")
var zoom = d3.zoom()
.on("zoom", zoomed);
//.scaleExtent([1 / 8, 4])
svg
.call(zoom).on("dblclick.zoom", null)
var g = svg.append("g");
function zoomed() {
g.attr("transform", d3.event.transform);
}
var color = d3.scaleOrdinal(d3.schemeCategory20);
var simulation = d3.forceSimulation()
//.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(function(d) {return d.distance/2;}).strength(1))
.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(10).strength(1))
.force("charge", d3.forceManyBody().strength(-10).distanceMax(100));
//.force("center", d3.forceCenter(1000, 1000));
//.force("y", d3.forceY(500))
//.force("x", d3.forceX(500));
//.force("collide",d3.forceCollide(.5));
//.force("collide",d3.forceCollide( function(d){return d.r + 8 }).iterations(4) );
var link = g.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr("stroke-width", 1 /*function(d) { return Math.sqrt(2); }*/)
.style("stroke", 'red');
var node = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r",3)
// .attr("cx", function(d) { return d.x; })
// .attr("cy", function(d) { return d.y; })
.on("dblclick", dblclick)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("title")
.text(function(d) { return d.id; });
simulation.nodes(nodes)
// .alphaDecay(0.5)
.velocityDecay(0.1)
.on("tick", ticked);
simulation.force("link")
.links(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("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
function dblclick(d) {
d.fx = null;
d.fy = null;
}
function dragstarted(d) {
//if (!d3.event.active) simulation.alphaTarget(0.3).restart();
simulation.restart();
// simulation.alpha -> redémarre la période de simulation
simulation.alpha(1.0);
d.fx = d.x;
d.fy = d.y;
}
//Grid
var grid = 50;
function dragged(d,i) {
//force.stop();
//var grid = 50;
var gx = Math.round(d3.event.x/grid)*grid;
var gy = Math.round(d3.event.y/grid)*grid;
d.fx = gx;
d.fy = gy;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
// console.log(d);
// d.fx = null;
// d.fy = null;
// d.fixed = true;
}
//Grid
var width = 7000;
var height = 7000;
var lineGraph = g.append("g")
.attr("width", width)
.attr("height", height);
// Using for loop to draw multiple horizontal lines
for (var j=grid; j <= width-grid; j=j+grid) {
lineGraph.append("svg:line")
.attr("x1", grid)
.attr("y1", j)
.attr("x2", width-grid)
.attr("y2", j)
.style("stroke", "rgb(119,119,119)")
.style("stroke-width", 1);
};
// Using for loop to draw multiple vertical lines
for (var j=grid; j <= height-grid; j=j+grid) {
lineGraph.append("svg:line")
.attr("x1", j)
.attr("y1", grid)
.attr("x2", j)
.attr("y2", height-grid)
.style("stroke", "rgb(119,119,119)")
.style("stroke-width", 1);
};
Your approach is correct, setting x and y defines the start position. However, they have to be numbers, not strings.
Therefore, instead of:
{
"id": "10",
"x": "125",
"y": "125"
}
It should be:
{
"id": "10",
"x": 125,
"y": 125
}
Here is your code with that change:
var nodes = [{
"id": "1",
"fx": "225",
"fy": "225"
}, {
"id": "2"
}, {
"id": "3"
}, {
"id": "4"
}, {
"id": "5"
}, {
"id": "6"
}, {
"id": "7"
}, {
"id": "8"
}, {
"id": "9"
}, {
"id": "10",
"x": 125,
"y": 125
}, {
"id": "11"
}, {
"id": "12"
}, {
"id": "13"
}, {
"id": "14"
}, {
"id": "15"
}]
var links = [{
"source": 1,
"target": 2
}, {
"source": 1,
"target": 3
}, {
"source": 1,
"target": 4
}, {
"source": 1,
"target": 5
}, {
"source": 1,
"target": 6
}, {
"source": 1,
"target": 7
}, {
"source": 1,
"target": 8
}, {
"source": 1,
"target": 9
}, {
"source": 1,
"target": 10
}, {
"source": 10,
"target": 11
}, {
"source": 10,
"target": 12
}, {
"source": 10,
"target": 13
}, {
"source": 10,
"target": 14
}, {
"source": 10,
"target": 15
}]
var svg = d3.select("svg")
var zoom = d3.zoom()
.on("zoom", zoomed);
//.scaleExtent([1 / 8, 4])
svg
.call(zoom).on("dblclick.zoom", null)
var g = svg.append("g");
function zoomed() {
g.attr("transform", d3.event.transform);
}
var color = d3.scaleOrdinal(d3.schemeCategory20);
var simulation = d3.forceSimulation()
//.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(function(d) {return d.distance/2;}).strength(1))
.force("link", d3.forceLink().id(function(d) {
return d.id;
}).distance(10).strength(1))
.force("charge", d3.forceManyBody().strength(-10).distanceMax(100));
//.force("center", d3.forceCenter(1000, 1000));
//.force("y", d3.forceY(500))
//.force("x", d3.forceX(500));
//.force("collide",d3.forceCollide(.5));
//.force("collide",d3.forceCollide( function(d){return d.r + 8 }).iterations(4) );
var link = g.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr("stroke-width", 1 /*function(d) { return Math.sqrt(2); }*/ )
.style("stroke", 'red');
var node = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 3)
// .attr("cx", function(d) { return d.x; })
// .attr("cy", function(d) { return d.y; })
.on("dblclick", dblclick)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("title")
.text(function(d) {
return d.id;
});
simulation.nodes(nodes)
// .alphaDecay(0.5)
.velocityDecay(0.1)
.on("tick", ticked);
simulation.force("link")
.links(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("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
}
function dblclick(d) {
d.fx = null;
d.fy = null;
}
function dragstarted(d) {
//if (!d3.event.active) simulation.alphaTarget(0.3).restart();
simulation.restart();
// simulation.alpha -> redémarre la période de simulation
simulation.alpha(1.0);
d.fx = d.x;
d.fy = d.y;
}
//Grid
var grid = 50;
function dragged(d, i) {
//force.stop();
//var grid = 50;
var gx = Math.round(d3.event.x / grid) * grid;
var gy = Math.round(d3.event.y / grid) * grid;
d.fx = gx;
d.fy = gy;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
// console.log(d);
// d.fx = null;
// d.fy = null;
// d.fixed = true;
}
//Grid
var width = 7000;
var height = 7000;
var lineGraph = g.append("g")
.attr("width", width)
.attr("height", height);
// Using for loop to draw multiple horizontal lines
for (var j = grid; j <= width - grid; j = j + grid) {
lineGraph.append("svg:line")
.attr("x1", grid)
.attr("y1", j)
.attr("x2", width - grid)
.attr("y2", j)
.style("stroke", "rgb(119,119,119)")
.style("stroke-width", 1);
};
// Using for loop to draw multiple vertical lines
for (var j = grid; j <= height - grid; j = j + grid) {
lineGraph.append("svg:line")
.attr("x1", j)
.attr("y1", grid)
.attr("x2", j)
.attr("y2", height - grid)
.style("stroke", "rgb(119,119,119)")
.style("stroke-width", 1);
};
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
display: flex;
font-family: sans-serif;
font-size: 75%;
}
/* SVG styles */
svg {
flex-basis: 100%;
}
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

Creating force layout node labels in d3.js

I have a force graph with colored nodes that I am trying to add labels to. Right now it has labels, but it's the small, hard to read ones native to the browser. How would I add labels that are easier to see?
<svg width="960" height="600"></svg>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory10);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
d3.json("got_relationships.json", function(error, graph) {
if (error) throw error;
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("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 5)
.attr("fill", function(d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
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("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
});
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;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
</script>
There is a confusion between "labels" and "tooltips". Traditionally, we name "labels" the texts that show up next to the nodes without user interaction, and we name "tooltips" the texts that show up when the user interacts with the nodes (for instance, hovering the nodes).
So, if you mean "labels", this is a solution: append the nodes as groups...
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.call(d3.drag().on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
And append the circles and the labels (as <text> elements) to them:
node.append("circle")
.attr("r", 5)
.attr("fill", function(d) { return color(d.group); });
node.append("text")
.attr("dx", 6)
.text(function(d) { return d.id; });
Here is a demo using (most of) your code, with a made up data:
var graph = {
nodes:[
{"id": "A", "group": 1},
{"id": "B", "group": 2},
{"id": "C", "group": 2},
{"id": "D", "group": 2},
{"id": "E", "group": 2},
{"id": "F", "group": 3},
{"id": "G", "group": 3},
{"id": "H", "group": 3},
{"id": "I", "group": 3}
],
links:[
{"source": "A", "target": "B", "value": 1},
{"source": "B", "target": "C", "value": 1},
{"source": "A", "target": "D", "value": 1},
{"source": "H", "target": "E", "value": 1},
{"source": "I", "target": "F", "value": 1},
{"source": "A", "target": "G", "value": 1},
{"source": "B", "target": "H", "value": 1},
{"source": "A", "target": "I", "value": 1},
]
};
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; }).distance(40))
.force("charge", d3.forceManyBody())
.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.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("circle")
.attr("r", 5)
.attr("fill", function(d) { return color(d.group); });
node.append("text")
.attr("dx", 6)
.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 + ")";
});
}
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;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.node circle {
stroke: #fff;
stroke-width: 1.5px;
}
.node text{
fill: #666;
font-family: Helvetica
}
<svg width="400" height="300"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>

Resources