d3js force button to allow/disable drag actions - javascript

My goal is to allow or disable the drag action on the nodes of my graph. I have this input : <input id="drag" name="drag" type="button" value="switch drag" />. I found some hints here and on this question.
As I said in a comment below, it doesn't work if I create nodes and links from an external json file. Is my function d3.json("graph.json", function(error, graph) {...}); not correct ?
Here's my json file:
{
"nodes": [
{"x": 260, "y": 210, "fixed": true},
{"x": 300, "y": 210, "fixed": true},
{"x": 260, "y": 260, "fixed": true},
{"x": 300, "y": 260, "fixed": true}
],
"links": [
{"source": 0, "target": 1},
{"source": 0, "target": 2},
{"source": 2, "target": 3},
{"source": 3, "target": 1}
]
}
And here's my code :
var width = 960, height = 500;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var myLinks = svg.selectAll(".link");
var myNodes = svg.selectAll(".node");
var force = d3.layout.force()
.size([width, height])
.charge(-400)
.linkDistance(40)
.on("tick", tick);
// Allows the drag actions
var drag = force.drag();
// Read the json file and creates the links and the nodes
d3.json("graph.json", function(error, graph) {
if (error) alert(error);
force
.nodes(graph.nodes)
.links(graph.links)
.start();
myLinks = myLinks.data(graph.links)
.enter()
.append("line")
.attr("class", "link");
myNodes = myNodes.data(graph.nodes)
.enter()
.append("circle")
.attr("class", "node")
.attr("r", 6)
.call(drag);
});
// Add properties to myLinks and myNodes
function tick() {
myLinks.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; });
myNodes.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
var dragOnOff = d3.select("#drag");
dragOnOff.on("click", function() {
myNodes.on('mousedown.drag', null);
});
var dragOnOff = d3.select("#drag");
var dragCallback = myNodes.property('__onmousedown.drag')['_'];
var draggable = true;
dragOnOff.on("click", function () {
myNodes.on('mousedown.drag', draggable ? null : dragCallback);
this.value = 'switch drag to ' + draggable;
draggable = !draggable;
});
I wish someone could answer me and my post can help someone else !
Thanks in advance!

Use on "click" for buttons instead of on "input".
var dragOnOff = d3.select('#drag');
var draggable = true;
dragOnOff.on('click', function () {
myNodes.on('mousedown.drag', draggable ? null : dragCallback);
draggable = !draggable;
});
http://jsfiddle.net/77yndu1e/2/

Related

How to use d3.line().curve when using x1, y1, and x2, y2?

I'm new to d3 and want to connect nodes using elbows. Searching online I found one solution similar to what is required Similar solution however this solution does not work for d3 v4+.
Additionally, I have found a viable approach from d3 named d3.line().curve(d3.curveStepAfter) (I'm not sure if this is the correct use) an example can be seen here. However I can not find a way to implement this for my current set-up which uses x1, y1, and x2, y2.
Data
var data = {
"nodes": [
{
"name": "Node 1",
fx: 50,
fy: 50
},
{
"name": "Node 2",
fx: 50,
fy: 100
},
{
"name": "Node 3",
fx: 200,
fy: 50
},
{
"name": "Node 4",
fx: 350,
fy: 50
},
{
"name": "Node 5",
fx: 200,
fy: 150
}].map(function(d, i) { return (d.fixed = true, d) }),
"links": [
{
"source": 0,
"target": 2
},
{
"source": 1,
"target": 2
},
{
"source": 2,
"target": 3
},
{
"source": 2,
"target": 4
}]
}
Code
var width = 560
var height = 500;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var force = self.force = d3.forceSimulation(data.nodes)
.force("link", d3.forceLink(data.links))
.force("collide", d3.forceCollide())
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", ticked);
var link = svg.selectAll("line.link")
.data(data.links)
.enter()
.append("line")
.style("stroke", "black")
.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 node = svg.selectAll("g.node")
.data(data.nodes)
.enter()
.append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
d3.selectAll(".node")
.append("circle")
.style("fill", "red")
.attr("r", 15);
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(event) {
if (!event.active) force.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}
function dragged(event) {
event.subject.fx = Math.ceil(event.x/5)*5;
event.subject.fy = Math.ceil(event.y/5)*5;
}
function dragended(event) {
if (!event.active) force.alphaTarget(0);
}
The code renders the nodes and connects them with straight lines, the goal is to add the curveStepAfter to create an elbow join as it looks neater for the type of diagram I require.
Any help is appreciated.
Here's a complete example.
<html>
<head>
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
<script>
const data = {
nodes: [
{ name: "Node 1", x: 50, y: 50 },
{ name: "Node 2", x: 50, y: 100 },
{ name: "Node 3", x: 200, y: 50 },
{ name: "Node 4", x: 350, y: 50 },
{ name: "Node 5", x: 200, y: 150 },
],
links: [
{ source: 0, target: 2 },
{ source: 1, target: 2 },
{ source: 2, target: 3 },
{ source: 2, target: 4 },
],
};
const segments = data.links.map(({ source, target }) => [
data.nodes[source],
data.nodes[target],
]);
const width = 560
const height = 500;
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
const line = d3.line()
.x(d => d.x)
.y(d => d.y)
.curve(d3.curveStep);
const link = svg.selectAll("path.link")
.data(segments)
.join("path")
.attr("d", line)
.attr("stroke", "black")
.attr("fill", "none")
const node = svg.selectAll("g.node")
.data(data.nodes)
.join("g")
.attr("class", "node")
.attr("transform", d => `translate(${d.x},${d.y})`)
.call(d3.drag()
.on("drag", dragged));
node.append("circle")
.style("fill", "red")
.attr("r", 15);
function dragged(event, d) {
d.x = Math.ceil(event.x / 5) * 5;
d.y = Math.ceil(event.y / 5) * 5;
link.attr("d", line);
d3.select(this)
.attr("transform", `translate(${d.x},${d.y})`)
}
</script>
</body>
</html>
The main idea is that
const segments = data.links.map(({ source, target }) => [
data.nodes[source],
data.nodes[target],
]);
is an array of line segments in the network. We can pass each segment to a d3.line() to create a <path> for the segment. I've also removed the dependency on d3-force, which isn't needed.

Increase Distance Between Nodes in Network/Chart

I'm trying to figure out a way to space out the nodes in my network graph for my d3.js code. I don't necessarily care about how the shape of the network will be when I load the page since I can just click and drag around the nodes to make any kind of shape I want. But I'm not really sure where I start in trying to space out my nodes. I searched around and nothing I found seems to work for me. Help is very much appreciated.
Here is a picture of what the network looks like when I load the page:
https://i.gyazo.com/919ad4bde39d9fe6a6b6c91548dbcc2f.png
Here is what I'd like for it to look like roughly (again, shape does not really matter, I'm just looking to get a little distance on the inital load):
https://i.gyazo.com/fefa29cf861e204bc83f34cbc2d1a17d.png
(I only have 8 rep so I can't upload pictures sorry)
Here's my code so far:
<!DOCTYPE html>
<style>
.links line {
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
</style>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Group Comments</title>
<script src="http://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<p> Not Ready: Group 6 Comments </p>
<svg width="960" height="600"></svg>
<script>
//fetches the svg
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
//Sets a color scale
var color = d3.scaleOrdinal(d3.schemeCategory20);
var strokeColor = d3.scaleLinear()
.domain([0, 1, 2])
.range(["white", "red", "green"]);
//Creates a force simulation
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))
//reads the JSON file
d3.json("NR6comments.json", function (error, graph) {
if (error) throw error;
//sets up the "links" between the nodes
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) })
.attr("stroke", function (d) { return strokeColor(d.value) });
//sets up the nodes
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 10)
.attr("fill", function (d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
//displays the ID number on a node when hovering over
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>
<p>Likes Chart</p>
</body>
</html>
I would greatly appreciate it if I could get some help with this problem. Thank you!
There are different ways to achieve what you want. The easiest one is setting the strength of your manyBody method. According to the API:
If strength is specified, sets the strength accessor to the specified number or function, re-evaluates the strength accessor for each node, and returns this force. A positive value causes nodes to attract each other, similar to gravity, while a negative value causes nodes to repel each other, similar to electrostatic charge.
Since I don't have access to your data, this is a simplified demo. The first version has no strength, just like your code:
var width = 400;
var height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var nodes = [{
name: "foo",
color: "blue"
}, {
name: "bar",
color: "green"
}, {
name: "baz",
color: "red"
}, {
name: "foofoo",
color: "yellow"
}, {
name: "foobar",
color: "blue"
}, {
name: "foobaz",
color: "green"
}, {
name: "barfoo",
color: "red"
}, {
name: "barbar",
color: "yellow"
}, {
name: "barbaz",
color: "blue"
}];
var links = [{
"source": 0,
"target": 1
}, {
"source": 0,
"target": 2
}, {
"source": 0,
"target": 3
}, {
"source": 1,
"target": 3
}, {
"source": 1,
"target": 4
}, {
"source": 2,
"target": 5
}, {
"source": 3,
"target": 6
}, {
"source": 1,
"target": 7
}, {
"source": 6,
"target": 8
}, {
"source": 0,
"target": 7
}, {
"source": 2,
"target": 6
}, {
"source": 3,
"target": 8
}];
var simulation = d3.forceSimulation()
.force("link", d3.forceLink())
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.selectAll(null)
.data(links)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
var node = svg.selectAll(null)
.data(nodes)
.enter()
.append("circle")
.attr("r", function(d) {
return d.r = 10;
})
.attr("stroke", "gray")
.attr("stroke-width", "2px")
.attr("fill", function(d) {
return d.color
});
simulation.nodes(nodes);
simulation.force("link")
.links(links);
simulation.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
}).attr("cy", function(d) {
return d.y
});
});
<script src="https://d3js.org/d3.v4.min.js"></script>
The second version, however, has the strength set to a negative value:
.force("charge", d3.forceManyBody().strength(-500))
Here it is:
var width = 400;
var height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var nodes = [{
name: "foo",
color: "blue"
}, {
name: "bar",
color: "green"
}, {
name: "baz",
color: "red"
}, {
name: "foofoo",
color: "yellow"
}, {
name: "foobar",
color: "blue"
}, {
name: "foobaz",
color: "green"
}, {
name: "barfoo",
color: "red"
}, {
name: "barbar",
color: "yellow"
}, {
name: "barbaz",
color: "blue"
}];
var links = [{
"source": 0,
"target": 1
}, {
"source": 0,
"target": 2
}, {
"source": 0,
"target": 3
}, {
"source": 1,
"target": 3
}, {
"source": 1,
"target": 4
}, {
"source": 2,
"target": 5
}, {
"source": 3,
"target": 6
}, {
"source": 1,
"target": 7
}, {
"source": 6,
"target": 8
}, {
"source": 0,
"target": 7
}, {
"source": 2,
"target": 6
}, {
"source": 3,
"target": 8
}];
var simulation = d3.forceSimulation()
.force("link", d3.forceLink())
.force("charge", d3.forceManyBody().strength(-500))
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.selectAll(null)
.data(links)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
var node = svg.selectAll(null)
.data(nodes)
.enter()
.append("circle")
.attr("r", function(d) {
return d.r = 10;
})
.attr("stroke", "gray")
.attr("stroke-width", "2px")
.attr("fill", function(d) {
return d.color
});
simulation.nodes(nodes);
simulation.force("link")
.links(links);
simulation.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
}).attr("cy", function(d) {
return d.y
});
});
<script src="https://d3js.org/d3.v4.min.js"></script>

how to draw polygon by d3.js

I have created five node using d3.js, and make links each other to make a polygon but they are not adjacent position to make a polygon, instead its making a random view other than a polygon.Am I missing something here, please take a look and suggest me.
var width = 300,
height = 300
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.gravity(1)
.linkDistance(200)
.charge(-100)
.size([width, height]);
var datajson = {
"nodes": [{
"name": "a",
"group": 2
}, {
"name": "b",
"group": 1
}, {
"name": "c",
"group": 1
}, {
"name": "d",
"group": 2
}, {
"name": "e",
"group": 2
}],
"links": [{
"source": 0,
"target": 1,
"value": 1,
"distance": 90
}, {
"source": 1,
"target": 2,
"value": 2,
"distance": 90
}, {
"source": 2,
"target": 3,
"value": 3,
"distance": 90
}, {
"source": 3,
"target": 4,
"value": 5,
"distance": 90
}, {
"source": 4,
"target": 0,
"value": 5,
"distance": 90
}]
}
force
.nodes(datajson.nodes)
.links(datajson.links)
.start();
var drag = force.drag()
.on("dragstart", dblclick);
var link = svg.selectAll(".link")
.data(datajson.links)
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(datajson.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("image")
.attr("x", -8)
.attr("y", -8)
.attr("width", 45)
.attr("height", 45)
.attr("xlink:href", function(d) {
var rnd = Math.floor(Math.random() * 64 + 1);
return null;
});
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.name
});
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("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
function dblclick(d) {
d3.select(this).classed("fixed", d.px = d.x, d.py = d.y);
console.log(d);
}
.link {
stroke: #dfdfdf;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
.link.red {
stroke: blue;
}
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
</body>
When you call
force
.nodes(datajson.nodes)
.links(datajson.links)
.start();
d3 randomly picks starting positions for the nodes, because they don't yet have x and y properties assigned.
However, prior to calling the code above, you could loop over each node and assign it x and y of the corners of the polygon, and they should maintain that relationship. They might not bounce around much though, because they'd already be at their intended position. In that case, you can slightly vary their position relative to their final intended position, by adding some random x and y displacement to the starting values.
Working example
The code that pre-positions the nodes is
var numNodes = datajson.nodes.length
var r = 20;
datajson.nodes.forEach(function(node, i) {
node.x = width/2 + r * Math.sin(2 * Math.PI * i / numNodes)
node.y = height/2 + r * Math.cos(2 * Math.PI * i / numNodes)
console.log(node)
})
I also had to tweak charge (-1000) and linkDistance (100) to make it work.
var width = 300,
height = 300
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.gravity(.5)
.linkDistance(100)
.charge(-1000)
.size([width, height]);
var datajson = {
"nodes": [{
"name": "a",
"group": 2
}, {
"name": "b",
"group": 1
}, {
"name": "c",
"group": 1
}, {
"name": "d",
"group": 2
}, {
"name": "e",
"group": 2
}],
"links": [{
"source": 0,
"target": 1,
"value": 1,
"distance": 90
}, {
"source": 1,
"target": 2,
"value": 2,
"distance": 90
}, {
"source": 2,
"target": 3,
"value": 3,
"distance": 90
}, {
"source": 3,
"target": 4,
"value": 5,
"distance": 90
}, {
"source": 4,
"target": 0,
"value": 5,
"distance": 90
}]
}
var numNodes = datajson.nodes.length
var r = 20;
datajson.nodes.forEach(function(node, i) {
node.x = width/2 + r * Math.sin(2 * Math.PI * i / numNodes)
node.y = height/2 + r * Math.cos(2 * Math.PI * i / numNodes)
console.log(node)
})
force
.nodes(datajson.nodes)
.links(datajson.links)
.start();
var drag = force.drag()
.on("dragstart", dblclick);
var link = svg.selectAll(".link")
.data(datajson.links)
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(datajson.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("image")
.attr("x", -8)
.attr("y", -8)
.attr("width", 45)
.attr("height", 45)
.attr("xlink:href", function(d) {
var rnd = Math.floor(Math.random() * 64 + 1);
return null;
});
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.name
});
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("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
function dblclick(d) {
d3.select(this).classed("fixed", d.px = d.x, d.py = d.y);
console.log(d);
}
.link {
stroke: #dfdfdf;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
.link.red {
stroke: blue;
}
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
</body>

D3 force directed graph direction

I'm new to D3, and am trying to make a graph similar to this example but a few things confuse me. I'm unsure when the example is referring to things built in to D3, or just the data set they are using.
Like here, I'm not sure about the id in d.id. as the example's data looks like this, which makes me think id is from the data. Or does it represent an index value?
"nodes": [
{"id": "Myriel", "group": 1},
{"id": "Napoleon", "group": 1}
],
// etc
"links": [
{"source": "Napoleon", "target": "Myriel", "value": 1},
{"source": "Mlle.Baptistine", "target": "Myriel", "value": 8},
{"source": "Mme.Magloire", "target": "Myriel", "value": 10},
//etc
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; })) <-- where is this from?
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
SO! In my code I get an error repeating thousands of times, 'Uncaught Error: missing: X', where X is the value of the first source in my links array. I can console.log my data and it looks fine, and the elements are rendering to the DOM, but all bunched up to the top left of the SVG. I don't know what is wrong. I guess I have 2 questions.
Could someone clarify about the example id thing?
What does my error mean?
Any help would be appreciated.
My code;
var graph
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
d3.json("./mock.json", function(json) {
var graph = json
console.log(graph);
console.log(graph.nodes);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink(graph.links))
.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.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 5)
//.attr("fill", function(d) { return color(d.id); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("title")
.text(function(d) {return d.text });
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;
}
});
Data:
{
"nodes":[
{"reference":5, "year": 0, "text":"The amount of time spent on video gaming is related negatively to academic achievement", "tags":["Academic disturbance"]},
{"reference":5, "year": 0, "text":"Digital addiction ranges from <1% and 38%", "tags":["Addiction"]},
{"reference":58, "year": 0, "text":"Patological video game play impacts negativelly academic achievement", "tags":["Addiction"]},
{"reference":77, "year": 2009, "text":"74% of adults have Internet access at home", "tags":["Adults"]},
{"reference":64, "year": 0, "text":"Apathetic users spend short times on web pages, follow no logical order, and make random selections", "tags":["Apathetic hypertext users3"]},
{"reference":8, "year": 0, "text":"49.8% of sessions are shorter than 5 seconds", "tags":["App usage"]}
],
"links": [
{"source":0,"target":2},
{"source":0,"target":6},
{"source":1,"target":6},
{"source":1,"target":3},
{"source":1,"target":2}
]
}
There are two issues with your code:
The index of 6, see in your links array, is not available in the nodes array. nodes has a length of 6, which means the largest index is 5. This causes an error even with the correct code. I have change 6 to 5 in my working example below, and I believe that is what you want.
Since the links are based on the index, you can simply return the index instead of the ID, i.e. d3.forceLink().id(function(d,i) { return i; }.
Here is the proof-of-function example:
var graph
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var json = {
"nodes": [{
"reference": 5,
"year": 0,
"text": "The amount of time spent on video gaming is related negatively to academic achievement",
"tags": ["Academic disturbance"]
}, {
"reference": 5,
"year": 0,
"text": "Digital addiction ranges from <1% and 38%",
"tags": ["Addiction"]
}, {
"reference": 58,
"year": 0,
"text": "Patological video game play impacts negativelly academic achievement",
"tags": ["Addiction"]
}, {
"reference": 77,
"year": 2009,
"text": "74% of adults have Internet access at home",
"tags": ["Adults"]
}, {
"reference": 64,
"year": 0,
"text": "Apathetic users spend short times on web pages, follow no logical order, and make random selections",
"tags": ["Apathetic hypertext users3"]
}, {
"reference": 8,
"year": 0,
"text": "49.8% of sessions are shorter than 5 seconds",
"tags": ["App usage"]
}],
"links": [{
"source": 0,
"target": 2
}, {
"source": 0,
"target": 5
}, {
"source": 1,
"target": 5
}, {
"source": 1,
"target": 3
}, {
"source": 1,
"target": 2
}
]
};
var graph = json;
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d, i) {
return i;
}))
.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.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 5)
//.attr("fill", function(d) { return color(d.id); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("title")
.text(function(d) {
return d.text
});
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;
}
.links line {
stroke: #aaa;
}
.nodes circle {
pointer-events: all;
stroke: none;
stroke-width: 40px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.8/d3.min.js"></script>
<svg width="500" height="200"></svg>

D3 label on a line in forced graph

There is example how to have a label on the node in a D3 forced graph. What I try to do is to have a label on the line instead.
Example of the label on node: http://bl.ocks.org/mbostock/2706022
This code will display the text for the line up in the left corner. It seems that it takes the x, y cordinates from the canvas and not from my line. How to fix this?
var labelLine = olinks.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text("eeeeee");
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
</style>
<body>
<script src="./Script/d3.v3/d3.v3.min.js"></script>
<script>
var graph = {
"nodes": [
{ "name": "App1-main", "group": 1 },
{ "name": "App2", "group": 1 },
{ "name": "App3", "group": 1 },
{ "name": "App4", "group": 1 },
{ "name": "Content-1", "group": 3 },
{ "name": "Content-1", "group": 3 },
{ "name": "Content-1", "group": 3 },
{ "name": "Content-1", "group": 3 },
{ "name": "Pontmercy", "group": 3 }
],
"links": [
{ "source": 1, "target": 0, "value": 1 },
{ "source": 2, "target": 0, "value": 1 },
{ "source": 0, "target": 3, "value": 1 }
]
};
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-300)
.linkDistance(60)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var drawGraph = function (graph) {
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var olinks = svg.selectAll("g.link")
.data(graph.links)
.enter().append("g")
.call(force.drag);
var link = olinks.append("line")
.attr("class", "link")
.style("stroke-width", function (d) { return Math.sqrt(d.value); });
var labelLine = olinks.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text("eeeeee");
var gnodes = svg.selectAll('g.gnode')
.data(graph.nodes)
.enter()
.append('g')
.classed('gnode', true);
var node = gnodes.append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function (d) { return color(d.group); })
.call(force.drag);
var labels = gnodes.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function (d) { return d.name; });
console.log(labels);
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; });
gnodes.attr("transform", function (d) {
return 'translate(' + [d.x, d.y] + ')';
});
});
};
drawGraph(graph);
</script>
This is an example that has correct behavior.
The key points are here:
1) You need to define link as SVG "g" element, so that you can define both lines and labels for each link, and that coordinates are computed correctly.
2) Label text must be centered horizontally (code: .attr("text-anchor", "middle")).
3) Inside tick(), you need to compute coordinate of the labels., as arithmetic mean between source and target node.
Hope this helps.
There also was another similar question recently.

Categories