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>
Related
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.
Please advise me what was wrong in the following code.
I want to create foce-directed graph by using D3.js
I cannot find where to correct in the following code.
Please advise me what was wrong in the following code.
I want to create foce-directed graph by using D3.js
I cannot find where to correct in the following code.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3 v5 force simulation</title>
</head>
<body>
<svg width="400" height="300"></svg>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
var nodesData = [
{id:"Korea", "group": 1},
{id:"USA", "group": 1},
{id:"France", "group": 1},
{id:"UK", "group": 1},
{id:"Japan", "group": 1},
{id:"Turkey", "group": 1},
]
var linksData = [
{ "source": 'Korea', "target": 'USA'},
{ "source": 'UK', "target": 'USA' },
{ "source": 'France', "target": 'Turkey' },
{ "source": 'Korea', "target": 'UK' },
{ "source": 'Japan', "target": 'Turkey' },
{ "source": 'Japan', "target": 'Korea' }
]
var link = d3.select("svg")
.selectAll("line")
.data(linksData)
.enter()
.append("line")
.attr("stroke-width", 1)
.attr("stroke", "black");
var node = d3.select("svg")
.selectAll("circle")
.data(nodesData)
.enter()
.append("circle")
.attr("r", 10)
.attr("fill", "LightSalmon")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var simulation = d3.forceSimulation()
.force("link", d3.forceLink())
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(200, 150));
simulation
.nodes(nodesData)
.on("tick", ticked);
simulation.force("link")
.links(linksData);
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>
I am studying d3.js and would like to know if it is possible to grow the nodes outward as generation of nodes goes down.
For example, in example below, I am forcing the location of node 0.
I would like to place the nodes so child nodes 1,2,3,4 are surrounding node 0, and grand child nodes 5,6,7,8 are placed at the outskirts of nodes 1,2,3,4 farther and farther away from node 0.
In the example below, the grandchild nodes 5,7 are closer to node 0 than child nodes.
Would it be possible to achieve what I am looking for?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3 v5 force simulation</title>
</head>
<body>
<svg width="600" height="400"></svg>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
var nodesData = [
{},
{},
{},
{},
{},
{},
{},
{},
{}
]
var linksData = [
{ "source": 0, "target": 1 },
{ "source": 0, "target": 2 },
{ "source": 0, "target": 3 },
{ "source": 0, "target": 4 },
{ "source": 1, "target": 5 },
{ "source": 1, "target": 6 },
{ "source": 1, "target": 7 },
{ "source": 2, "target": 8 }
]
var link = d3.select("svg")
.selectAll("line")
.data(linksData)
.enter()
.append("line")
.attr("stroke-width", 1)
.attr("stroke", "black");
var node = d3.select("svg")
.selectAll("circle")
.data(nodesData)
.enter()
.append("circle")
.attr("r", 7)
.attr("fill", "LightSalmon");
const svg = d3.select("svg");
const sWidth = +svg.attr("width");
const sHeight = +svg.attr("height");
nodesData[0].fx = sWidth / 2;
nodesData[0].fy = sHeight / 2;
var simulation = d3.forceSimulation()
.force("collide", d3.forceCollide()
.radius((d)=>{return 10})
.strength(1.0)
.iterations(1))
.force("x", d3.forceX().strength(0.7))
.force("y", d3.forceY().strength(0.7))
.force("link", d3.forceLink())
.force("charge", d3.forceManyBody().strength(-6000))
.force("center", d3.forceCenter(sWidth/2, sHeight/2));
simulation
.nodes(nodesData)
.on("tick", ticked);
simulation.force("link")
.links(linksData);
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; });
}
</script>
</body>
</html>
Looking at the documentation there is a distance method you can pass to force a fixed length for the links. Although you will probably need to play with other values to accomplish exactly the kind of equilibrium you are after.
.distance(function(d) {return d.distance;})
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3 v5 force simulation</title>
</head>
<body>
<svg width="600" height="400"></svg>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
var nodesData = [
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{}
]
var linksData = [
{ "source": 0, "target": 1 },
{ "source": 0, "target": 2 },
{ "source": 0, "target": 3 },
{ "source": 0, "target": 4 },
{ "source": 1, "target": 5 },
{ "source": 1, "target": 6 },
{ "source": 1, "target": 7 },
{ "source": 2, "target": 8 },
{ "source": 2, "target": 9 },
{ "source": 2, "target": 10 },
{ "source": 2, "target": 11 }
]
var link = d3.select("svg")
.selectAll("line")
.data(linksData)
.enter()
.append("line")
.attr("stroke-width", 1)
.attr("stroke", "black");
var node = d3.select("svg")
.selectAll("circle")
.data(nodesData)
.enter()
.append("circle")
.attr("r", 15)
.attr("fill", "LightSalmon");
const svg = d3.select("svg");
const sWidth = +svg.attr("width");
const sHeight = +svg.attr("height");
nodesData[0].fx = sWidth / 2;
nodesData[0].fy = sHeight / 2;
var simulation = d3.forceSimulation()
.force("collide", d3.forceCollide()
.radius(40)
.strength(1)
.iterations(1))
.force("x", d3.forceX().strength(0))
.force("y", d3.forceY().strength(0))
.force("link", d3.forceLink().distance(function(d) {return 30;}))
.force("charge", d3.forceManyBody().strength(0))
.force("center", d3.forceCenter(sWidth/2, sHeight/2));
simulation
.nodes(nodesData)
.on("tick", ticked);
simulation.force("link")
.links(linksData);
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; });
}
</script>
</body>
</html>
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>
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.