Nodes drawn randomly in Force Layout graph - d3.js

<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Force Layout Example 1</title>
<style>
.node {
fill: #ccc;
stroke: #fff;
stroke-width: 2px;
}
.link {
stroke: #777;
stroke-width: 2px;
}
.line {
stroke: #777;
stroke-width: 2px;
}
</style>
</head>
<body>
<script src='http://d3js.org/d3.v3.min.js'></script>
<script>
var width = 640,
height = 480;
var nodes = [
{ "x": 200, "y": 200 },
{ "x": 500, "y": 300 },
{ "x": 500, "y": 100 },
//{ "x": 650, "y": 100 },
];
//var nodes = [
// { "x": 200, "y": 200 },
// { "x": 500, "y": 300 },
//{ "x": 500, "y": 100 },
//];
//var links = [
// { source: 0, target: 1 },
// { source: 1, target: 2 },
//];
var links = [
{ source: 0, target: 1 },
{ source: 0, target: 2 },
//{ source: 1, target: 3 },
];
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
var force = d3.layout.force()
.size([width, height])
.nodes(nodes)
.links(links);
force.linkDistance(75);
var link = svg.selectAll('.link')
.data(links)
.enter().append('line')
.attr('class', 'link');
var node = svg.selectAll('.node')
.data(nodes)
.enter().append('rect')
.attr('class', 'node');
var subnode = svg.selectAll('.subnode')
.data(nodes)
.enter().append('circle')
.attr('class', 'subnode');
var subnode2 = svg.selectAll('.subnode2')
.data(nodes)
.enter().append('circle')
.attr('class', 'subnode2');
force.on('end', function() {
node.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("width", 50)
.attr("height", 20);
subnode.attr('r', width/250)
.attr('cx', function(d) { return (d.x); })
.attr('cy', function(d) { return d.y + 10; });
subnode2.attr('r', width/250)
.attr('cx', function(d) { return d.x+50; })
.attr('cy', function(d) { return d.y + 10; });
link.attr('x1', function(d) { return d.source.x; })
.attr('y1', function(d) { return d.source.y+ 10; })
.attr('x2', function(d) { return d.target.x+50; })
.attr('y2', function(d) { return d.target.y+ 10; });
});
force.start();
var line;
function mousedown() {
var m = d3.mouse(this);
//alert(m[0]+"---"+m[1]);
line = svg.append("line")
.attr('x1', m[0])
.attr('y1', m[1])
.attr('x2', m[0])
.attr('y2', m[1]);
svg.on("mousemove", mousemove);
}
function mousemove() {
var m = d3.mouse(this);
line.attr('x2', m[0])
.attr('y2', m[1]);
}
function mouseup() {
svg.on("mousemove", null);
}
</script>
</body>
</html>
The above solution gives below result:
The problem is I dont understand why the graph is drawn reverse and moreover in above code I have commented out some nodes and links if you uncomment them then there is more chaos the whole nodes are drawn in complete random order i.e. increasing more nodes and links create more chaos.
See the JSBIN : http://jsbin.com/yuyolof/edit?html

Take a look at this jsbin http://jsbin.com/himutohimu/1/edit?html,css,output
(I've added a bit too much info in this just to have a better look at what's going on)
You have two subnodes that have the same node data. You are positioning them on "end" like this:
subnode.attr('r', width/250) // black nodes
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y + 10; });
subnode2.attr('r', width/250) // red nodes
.attr('cx', function(d) { return d.x + 50; })
.attr('cy', function(d) { return d.y + 10; });
I've colored the nodes differently in order to better see how this works.
In order for your lines to connect to one kind of subnodes you need to either follow the x and y of the black nodes or the x and y of the red nodes:
// x1 and y1 are the starting point of the line, so in order to follow the
// red nodes, we need to move accordingly with +50 for x and +10 for y.
// the same goes for x2, y2 which are the coordinates for the end of the line
link.attr('x1', function(d) { return d.source.x + 50; })
.attr('y1', function(d) { return d.source.y + 10; })
.attr('x2', function(d) { return d.target.x + 50; })
.attr('y2', function(d) { return d.target.y + 10; });
//Or if you need your lines to follow the black nodes/ dots then x1, y1
// and x2,y2 need to move accordingly to your subnode's x and y,
// so x as it is and y plus 10
// it is one or the other
link.attr('x1', function(d) { return d.source.x; })
.attr('y1', function(d) { return d.source.y + 10; })
.attr('x2', function(d) { return d.target.x; })
.attr('y2', function(d) { return d.target.y + 10; });
So, it is a matter of what nodes (dots) you want to connect and then move the line according to their respective x and y.
Hope this helps!
Good luck!
Edit: How this works with more nodes: http://jsbin.com/nodaruwere/1/edit?html,output

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>

Locate link labels and move the labels along with the links

I am developing a project on pattern representation through directed graphs. I have reached the point of loading it from a json, placing the node labels, the links between them and that both the links and the node labels move when moving the nodes. However, I am facing difficulties in placing the link labels ("type" in the json) in position (midpoint of the links) and linking to the movement of the links. Any idea of resolution and explanation? Thanks!!!
The actual code can be found at the link (https://jsfiddle.net/obordies25/wmLeganx/2/)
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>[AF-PATTERN-EDITOR]</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="robots" content="noindex, nofollow">
<meta name="googlebot" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style id="compiled-css" type="text/css">
.node {
fill:#ccc;
stroke: #fff;
stroke-width: 2px;
}
.node-active {
stroke: #555;
stroke-width: 1.5px;
}
link-active {
stroke: #555;
stroke-opacity: 5;
}
line {
stroke: rgb(212, 212, 212);
stroke-width: 1px;
shape-rendering: crispEdges;
}
.overlay {
fill: none;
pointer-events: all;
}
.link{
stroke: #ccc;
stroke-width: 2px;
}
svg {
box-sizing: border-box;
border: 1px solid rgb(212, 212, 212);
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script type="text/javascript">
var width = 960,
height = 500,
resolution = 150,
r = 15;
var graph = {
"nodes": [
{"task": "1", "label": "1", "social": "I", "id": 1, "x": 150, "y": 450},
{"task": "2", "label": "2", "social": "G", "id": 2, "x": 300, "y": 150},
{"task": "3", "label": "3", "social": "T", "id": 3, "x": 450, "y": 300}
],
"links": [
{"source": "1", "target": "2", "type": "N:1"},
{"source": "2", "target": "3", "type": "1:N"},
{"source": "1", "target": "3", "type": "1:1"}
]
}
/*d3.json("graph.json", function (error, graph) {
if (error) throw error;
update(graph.links, graph.nodes);
})*/
var margin = {
top: -5,
right: -5,
bottom: -5,
left: -5
};
var colors = d3.scale.category20();
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
var force = d3.layout.force()
.charge(-200)
.linkDistance(50)
.size([width + margin.left + margin.right, height + margin.top + margin.bottom])
//.nodes(graph.nodes);
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on('drag', dragged);
svg.selectAll('.vertical')
.data(d3.range(1, width / resolution))
.enter().append('line')
.attr('class', 'vertical')
.attr('x1', function(d) { return d * resolution; })
.attr('y1', 0)
.attr('x2', function(d) { return d * resolution; })
.attr('y2', height);
svg.selectAll('.horizontal')
.data(d3.range(1, height / resolution))
.enter().append('line')
.attr('class', 'horizontal')
.attr('x1', 0)
.attr('y1', function(d) { return d * resolution; })
.attr('x2', width)
.attr('y2', function(d) { return d * resolution; });
svg.append('defs').append('marker')
.attr({'id':'arrowhead',
'viewBox':'-0 -5 10 10',
'refX':40,
'refY':0,
'orient':'auto',
'markerWidth':5,
'markerHeight':5,
'xoverflow':'visible'})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#777')
.style('stroke','none');
var link = svg.selectAll("svg.link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.attr('marker-end','url(#arrowhead)')
.attr("data-source", function (d) {
return d.source;
})
.attr("data-target", function (d) {
return d.target;
})
.attr("x1", function (d) {
for (let node of graph.nodes) {
if (node.task === d.source)
return node.x;
}
})
.attr("x2", function (d) {
for (let node of graph.nodes) {
if (node.task === d.target)
return node.x;
}
})
.attr("y1", function (d) {
for (let node of graph.nodes) {
if (node.task === d.source)
return node.y;
}
})
.attr("y2", function (d) {
for (let node of graph.nodes) {
if (node.task === d.target)
return node.y;
}
})
.style("stroke-width", function(d) {
return Math.sqrt(d.value);
});
var node = svg.selectAll("svg.node")
.data(graph.nodes)
.enter().append('g');
node.append("circle")
.attr("class", "node")
.attr('node-id', d => d.social)
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', r * 2)
.call(drag);//
node.append("text")
.attr("class", "label")
.attr('node-id', d => d.social)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.attr('dx', function(d) { return d.x; })
.attr('dy', function(d) { return d.y; })
.text(function(d) { return d.social; });
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 dragged(d) {
var x = d3.event.x,
y = d3.event.y,
gridX = round(Math.max(r, Math.min(width - r, x)), resolution),
gridY = round(Math.max(r, Math.min(height - r, y)), resolution);
d3.select(this).attr('cx', d.x = gridX).attr('cy', d.y = gridY);
d3.select(this).attr('dx', d.x = gridX).attr('dy', d.y = gridY);
d3.selectAll(`[data-source='${d.task}']`).attr('x1', d.x).attr('y1', d.y);
d3.selectAll(`[data-target='${d.task}']`).attr('x2', d.x).attr('y2', d.y);
d3.select(`text[node-id='${d.social}']`).attr('dx', d.x).attr('dy', d.y)
}
function round(p, n) {
return p % n < n / 2 ? p - (p % n) : p + n - (p % n);
}
</script>
</body>
<html>
Create link labels as <g> elements with a <circle> and a <text> under each one:
const links = graph.links.map(l => {
const source = graph.nodes.find(n => n.task === l.source);
const target = graph.nodes.find(n => n.task === l.target);
return {...l, source, target};
});
const linkLabel = svg.selectAll("g.link-label")
.data(links)
.enter()
.append("g")
.classed('link-label', true)
.attr('transform', d =>
`translate(${(d.source.x + d.target.x) / 2},${(d.source.y + d.target.y) / 2})`);
linkLabel.append('circle')
.attr('r', 20);
linkLabel.append('text')
.text(d => d.type)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.style('stroke', 'none')
.style('fill', 'black')
See it's working in the snippet:
var width = 960,
height = 500,
resolution = 150,
r = 15;
var graph = {
"nodes": [
{"task": "1", "label": "1", "social": "I", "id": 1, "x": 150, "y": 450},
{"task": "2", "label": "2", "social": "G", "id": 2, "x": 300, "y": 150},
{"task": "3", "label": "3", "social": "T", "id": 3, "x": 450, "y": 300}
],
"links": [
{"source": "1", "target": "2", "type": "N:1"},
{"source": "2", "target": "3", "type": "1:N"},
{"source": "1", "target": "3", "type": "1:1"}
]
}
var margin = {
top: -5,
right: -5,
bottom: -5,
left: -5
};
var colors = d3.scale.category20();
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
var force = d3.layout.force()
.charge(-200)
.linkDistance(50)
.size([width + margin.left + margin.right, height + margin.top + margin.bottom])
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on('drag', dragged);
svg.selectAll('.vertical')
.data(d3.range(1, width / resolution))
.enter().append('line')
.attr('class', 'vertical')
.attr('x1', function(d) { return d * resolution; })
.attr('y1', 0)
.attr('x2', function(d) { return d * resolution; })
.attr('y2', height);
svg.selectAll('.horizontal')
.data(d3.range(1, height / resolution))
.enter().append('line')
.attr('class', 'horizontal')
.attr('x1', 0)
.attr('y1', function(d) { return d * resolution; })
.attr('x2', width)
.attr('y2', function(d) { return d * resolution; });
svg.append('defs').append('marker')
.attr({'id':'arrowhead',
'viewBox':'-0 -5 10 10',
'refX':40,
'refY':0,
'orient':'auto',
'markerWidth':5,
'markerHeight':5,
'xoverflow':'visible'})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#777')
.style('stroke','none');
var link = svg.selectAll("line.link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.attr('marker-end','url(#arrowhead)')
.attr("data-source", function (d) {
return d.source;
})
.attr("data-target", function (d) {
return d.target;
})
.attr("x1", function (d) {
for (let node of graph.nodes) {
if (node.task === d.source)
return node.x;
}
})
.attr("x2", function (d) {
for (let node of graph.nodes) {
if (node.task === d.target)
return node.x;
}
})
.attr("y1", function (d) {
for (let node of graph.nodes) {
if (node.task === d.source)
return node.y;
}
})
.attr("y2", function (d) {
for (let node of graph.nodes) {
if (node.task === d.target)
return node.y;
}
})
.style("stroke-width", function(d) {
return Math.sqrt(d.value);
});
const links = graph.links.map(l => {
const source = graph.nodes.find(n => n.task === l.source);
const target = graph.nodes.find(n => n.task === l.target);
return {...l, source, target};
});
const linkLabel = svg.selectAll("g.link-label")
.data(links)
.enter()
.append("g")
.attr("class", "link-label")
.attr('transform', d => `translate(${(d.source.x + d.target.x) / 2},${(d.source.y + d.target.y) / 2})`);
linkLabel.append('circle')
.attr('r', 20);
linkLabel.append('text')
.text(d => d.type)
.classed('label', true)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.style('stroke', 'none')
.style('fill', 'black')
console.log('L: ', graph.links);
var node = svg.selectAll("svg.node")
.data(graph.nodes)
.enter().append('g');
node.append("circle")
.attr("class", "node")
.attr('node-id', d => d.social)
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', r * 2)
.call(drag);//
node.append("text")
.attr("class", "label")
.attr('node-id', d => d.social)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.attr('dx', function(d) { return d.x; })
.attr('dy', function(d) { return d.y; })
.text(function(d) { return d.social; });
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; });
linkLabel.attr('transform', d => `translate(${(d.source.x + d.target.x) / 2},${(d.source.y + d.target.y) / 2})`);
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
function dragged(d) {
var x = d3.event.x,
y = d3.event.y,
gridX = round(Math.max(r, Math.min(width - r, x)), resolution),
gridY = round(Math.max(r, Math.min(height - r, y)), resolution);
d3.select(this).attr('cx', d.x = gridX).attr('cy', d.y = gridY);
d3.select(this).attr('dx', d.x = gridX).attr('dy', d.y = gridY);
d3.selectAll(`[data-source='${d.task}']`).attr('x1', d.x).attr('y1', d.y);
d3.selectAll(`[data-target='${d.task}']`).attr('x2', d.x).attr('y2', d.y);
d3.select(`text[node-id='${d.social}']`).attr('dx', d.x).attr('dy', d.y)
linkLabel.attr('transform', d => `translate(${(d.source.x + d.target.x) / 2},${(d.source.y + d.target.y) / 2})`);
}
function round(p, n) {
return p % n < n / 2 ? p - (p % n) : p + n - (p % n);
}
.node, .link-label {
fill:#ccc;
stroke: #fff;
stroke-width: 2px;
}
.node-active {
stroke: #555;
stroke-width: 1.5px;
}
link-active {
stroke: #555;
stroke-opacity: 5;
}
line {
stroke: rgb(212, 212, 212);
stroke-width: 1px;
shape-rendering: crispEdges;
}
.overlay {
fill: none;
pointer-events: all;
}
.link{
stroke: #ccc;
stroke-width: 2px;
}
svg {
box-sizing: border-box;
border: 1px solid rgb(212, 212, 212);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>

d3js Moving the labels next to the nodes

I am developing a project on pattern representation through graphs. I have come to retrieve the data from a json to generate the graphs and locate the labels associated with the nodes (I would also like to locate the labels corresponding to the links). However, when moving the nodes, I am unable to move the labels. Any help? See the code here:
var width = 960,
height = 500,
resolution = 150,
r = 15;
var graph = {
"nodes": [
{"task": "1", "label": "1", "social": "I", "id": 1, "x": 150, "y": 450},
{"task": "2", "label": "2", "social": "G", "id": 2, "x": 300, "y": 150},
{"task": "3", "label": "3", "social": "T", "id": 3, "x": 450, "y": 300}
],
"links": [
{"source": "1", "target": "2", "type": "N:1"},
{"source": "2", "target": "3", "type": "1:N"},
{"source": "1", "target": "3", "type": "1:1"}
]
}
var margin = {
top: -5,
right: -5,
bottom: -5,
left: -5
};
var colors = d3.scale.category20();
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
var force = d3.layout.force()
.charge(-200)
.linkDistance(50)
.size([width + margin.left + margin.right, height + margin.top + margin.bottom])
.nodes(graph.nodes);
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on('drag', dragged);
svg.selectAll('.vertical')
.data(d3.range(1, width / resolution))
.enter().append('line')
.attr('class', 'vertical')
.attr('x1', function(d) { return d * resolution; })
.attr('y1', 0)
.attr('x2', function(d) { return d * resolution; })
.attr('y2', height);
svg.selectAll('.horizontal')
.data(d3.range(1, height / resolution))
.enter().append('line')
.attr('class', 'horizontal')
.attr('x1', 0)
.attr('y1', function(d) { return d * resolution; })
.attr('x2', width)
.attr('y2', function(d) { return d * resolution; });
svg.append('defs').append('marker')
.attr({'id':'arrowhead',
'viewBox':'-0 -5 10 10',
'refX':25,
'refY':0,
'orient':'auto',
'markerWidth':5,
'markerHeight':5,
'xoverflow':'visible'})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#777')
.style('stroke','none');
var link = svg.selectAll("svg.link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.attr('marker-end','url(#arrowhead)')
.attr("data-source", function (d) {
return d.source;
})
.attr("data-target", function (d) {
return d.target;
})
.attr("x1", function (d) {
for (let node of graph.nodes) {
if (node.task === d.source)
return node.x;
}
})
.attr("x2", function (d) {
for (let node of graph.nodes) {
if (node.task === d.target)
return node.x;
}
})
.attr("y1", function (d) {
for (let node of graph.nodes) {
if (node.task === d.source)
return node.y;
}
})
.attr("y2", function (d) {
for (let node of graph.nodes) {
if (node.task === d.target)
return node.y;
}
})
.style("stroke-width", function(d) {
return Math.sqrt(d.value);
});
var node = svg.selectAll("svg.node")
.data(graph.nodes)
.enter().append('g');
node.append("circle")
.attr("class", "node")
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', r)
.call(drag);//
//text element
node.append("title")
.text(function (d) {return d.social});
node.append("text")
//.attr("dx", )
//.attr("dy", 6)
.attr("class", "font")
.attr('dx', function(d) { return d.x-6; })
.attr('dy', function(d) { return d.y+6; })
.text(function(d) { return d.social; });
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 dragged(d) {
var x = d3.event.x,
y = d3.event.y,
gridX = round(Math.max(r, Math.min(width - r, x)), resolution),
gridY = round(Math.max(r, Math.min(height - r, y)), resolution);
d3.select(this).attr('cx', d.x = gridX).attr('cy', d.y = gridY);
d3.select(this).attr('dx', d.x = gridX).attr('dy', d.y = gridY);
d3.selectAll(`[data-source='${d.task}']`).attr('x1', d.x).attr('y1', d.y);
d3.selectAll(`[data-target='${d.task}']`).attr('x2', d.x).attr('y2', d.y);
}
function round(p, n) {
return p % n < n / 2 ? p - (p % n) : p + n - (p % n);
}
.node {
fill:#ccc;
stroke: #fff;
stroke-width: 2px;
}
.node-active {
stroke: #555;
stroke-width: 1.5px;
}
link-active {
stroke: #555;
stroke-opacity: 5;
}
line {
stroke: rgb(212, 212, 212);
stroke-width: 1px;
shape-rendering: crispEdges;
}
.overlay {
fill: none;
pointer-events: all;
}
.link{
stroke: #ccc;
stroke-width: 2px;
}
svg {
box-sizing: border-box;
border: 1px solid rgb(212, 212, 212);
}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>[D3]</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="robots" content="noindex, nofollow">
<meta name="googlebot" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<body>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</body>
Add an identification attribute to <circle> and <text> elements:
...attr('node-id', d => d.social)
Move the text along with the circle:
d3.select(`text[node-id='${d.social}']`).attr('dx', d.x).attr('dy', d.y)
Use text-anchor and alignment-baseline for <text> labels instead of offsets (+6/-6):
...attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.attr('dx', function(d) { return d.x; })
.attr('dy', function(d) { return d.y; })
I also enlarged the <circle>s to make them easy to drag...
var width = 960,
height = 500,
resolution = 150,
r = 15;
var graph = {
"nodes": [
{"task": "1", "label": "1", "social": "I", "id": 1, "x": 150, "y": 450},
{"task": "2", "label": "2", "social": "G", "id": 2, "x": 300, "y": 150},
{"task": "3", "label": "3", "social": "T", "id": 3, "x": 450, "y": 300}
],
"links": [
{"source": "1", "target": "2", "type": "N:1"},
{"source": "2", "target": "3", "type": "1:N"},
{"source": "1", "target": "3", "type": "1:1"}
]
}
var margin = {
top: -5,
right: -5,
bottom: -5,
left: -5
};
var colors = d3.scale.category20();
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
var force = d3.layout.force()
.charge(-200)
.linkDistance(50)
.size([width + margin.left + margin.right, height + margin.top + margin.bottom])
.nodes(graph.nodes);
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on('drag', dragged);
svg.selectAll('.vertical')
.data(d3.range(1, width / resolution))
.enter().append('line')
.attr('class', 'vertical')
.attr('x1', function(d) { return d * resolution; })
.attr('y1', 0)
.attr('x2', function(d) { return d * resolution; })
.attr('y2', height);
svg.selectAll('.horizontal')
.data(d3.range(1, height / resolution))
.enter().append('line')
.attr('class', 'horizontal')
.attr('x1', 0)
.attr('y1', function(d) { return d * resolution; })
.attr('x2', width)
.attr('y2', function(d) { return d * resolution; });
svg.append('defs').append('marker')
.attr({'id':'arrowhead',
'viewBox':'-0 -5 10 10',
'refX':40,
'refY':0,
'orient':'auto',
'markerWidth':5,
'markerHeight':5,
'xoverflow':'visible'})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#777')
.style('stroke','none');
var link = svg.selectAll("svg.link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.attr('marker-end','url(#arrowhead)')
.attr("data-source", function (d) {
return d.source;
})
.attr("data-target", function (d) {
return d.target;
})
.attr("x1", function (d) {
for (let node of graph.nodes) {
if (node.task === d.source)
return node.x;
}
})
.attr("x2", function (d) {
for (let node of graph.nodes) {
if (node.task === d.target)
return node.x;
}
})
.attr("y1", function (d) {
for (let node of graph.nodes) {
if (node.task === d.source)
return node.y;
}
})
.attr("y2", function (d) {
for (let node of graph.nodes) {
if (node.task === d.target)
return node.y;
}
})
.style("stroke-width", function(d) {
return Math.sqrt(d.value);
});
var node = svg.selectAll("svg.node")
.data(graph.nodes)
.enter().append('g');
node.append("circle")
.attr("class", "node")
.attr('node-id', d => d.social)
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', r * 2)
.call(drag);//
node.append("text")
.attr("class", "label")
.attr('node-id', d => d.social)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.attr('dx', function(d) { return d.x; })
.attr('dy', function(d) { return d.y; })
.text(function(d) { return d.social; });
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 dragged(d) {
var x = d3.event.x,
y = d3.event.y,
gridX = round(Math.max(r, Math.min(width - r, x)), resolution),
gridY = round(Math.max(r, Math.min(height - r, y)), resolution);
d3.select(this).attr('cx', d.x = gridX).attr('cy', d.y = gridY);
d3.select(this).attr('dx', d.x = gridX).attr('dy', d.y = gridY);
d3.selectAll(`[data-source='${d.task}']`).attr('x1', d.x).attr('y1', d.y);
d3.selectAll(`[data-target='${d.task}']`).attr('x2', d.x).attr('y2', d.y);
d3.select(`text[node-id='${d.social}']`).attr('dx', d.x).attr('dy', d.y)
}
function round(p, n) {
return p % n < n / 2 ? p - (p % n) : p + n - (p % n);
}
.node {
fill:#ccc;
stroke: #fff;
stroke-width: 2px;
}
.node-active {
stroke: #555;
stroke-width: 1.5px;
}
link-active {
stroke: #555;
stroke-opacity: 5;
}
line {
stroke: rgb(212, 212, 212);
stroke-width: 1px;
shape-rendering: crispEdges;
}
.overlay {
fill: none;
pointer-events: all;
}
.link{
stroke: #ccc;
stroke-width: 2px;
}
svg {
box-sizing: border-box;
border: 1px solid rgb(212, 212, 212);
}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>[D3]</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="robots" content="noindex, nofollow">
<meta name="googlebot" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<body>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</body>

d3 shrinking radar chart shape using attrTween

Hi I'm trying to use attrTween for my radar chart.
I made it happen for arc chart so I wanted to apply the same logic to my radar chart but it didn't work out.
can anyone let me know which part I missed?
I would like to shrink the radar chart on 'click'.
the code is as below.
d3.selectAll('.btn').on('click', function(d) {
let selectedone = d3.select(this).attr('class').split(' ')[1]
console.log(selectedone);
d3.select(`.radar${selectedone}`)
.transition()
.duration(1000)
.attrTween('d', shrinkRadar(0))
function shrinkRadar(target) {
return function(d) {
console.log(d)
let interpolate = d3.interpolate(rscale(d.value), target)
return function(t) {
console.log(d.radius);
d.radius = interpolate(t)
return radarLine(d)
}
}
}
// .attrTween('d',shrinkRadar(finalvalue))
})
Full code in the following link
https://codepen.io/jotnajoa/pen/dypJzmZ
Your radar chart's line functions take an array of data to output a path (line and area fill). You are attempting, though, to operate the attrTween like it's manipulating a single piece of data and not an array. Take a look at this quick re-write:
function shrinkRadar(target) {
return function(d) {
// create an array of interpolators
// one for each point in the line
// that run the value from starting to 0
var interps = d.map( da => d3.interpolate(da.value, target));
return function(t) {
// for each point call it's interpolator
// and re-assign the value
d.forEach( (da,i) => {
da.value = interps[i](t);
});
// regenerate path with new value
return radarLine(d)
}
}
}
Running code:
let margin = {
top: 100,
bottom: 100,
left: 100,
right: 100
}
let width = Math.min(700, window.innerWidth - 10) - margin.left - margin.right;
let height = Math.min(width, window.innerHeight - margin.top - margin.bottom - 20);
var data = [
[ //iPhone
{
axis: "Battery Life",
value: 0.22
},
{
axis: "Brand",
value: 0.28
},
{
axis: "Contract Cost",
value: 0.29
},
{
axis: "Design And Quality",
value: 0.17
},
{
axis: "Have Internet Connectivity",
value: 0.22
},
{
axis: "Large Screen",
value: 0.02
},
{
axis: "Price Of Device",
value: 0.21
},
{
axis: "To Be A Smartphone",
value: 0.50
}
],
[ //Samsung
{
axis: "Battery Life",
value: 0.27
},
{
axis: "Brand",
value: 0.16
},
{
axis: "Contract Cost",
value: 0.35
},
{
axis: "Design And Quality",
value: 0.13
},
{
axis: "Have Internet Connectivity",
value: 0.20
},
{
axis: "Large Screen",
value: 0.13
},
{
axis: "Price Of Device",
value: 0.35
},
{
axis: "To Be A Smartphone",
value: 0.38
}
],
[ //Nokia Smartphone
{
axis: "Battery Life",
value: 0.26
},
{
axis: "Brand",
value: 0.10
},
{
axis: "Contract Cost",
value: 0.30
},
{
axis: "Design And Quality",
value: 0.14
},
{
axis: "Have Internet Connectivity",
value: 0.22
},
{
axis: "Large Screen",
value: 0.04
},
{
axis: "Price Of Device",
value: 0.41
},
{
axis: "To Be A Smartphone",
value: 0.30
}
]
];
var color = d3.scaleOrdinal()
.range(['#6678D9', '#61F06C', '#FF36AA'])
var radarChartOptions = {
w: width,
h: height,
margin: margin,
maxValue: 0.5,
levels: 5,
roundStrokes: true,
color: color
};
// blue, green pink inorder
RadarChart('.radarChart', data, radarChartOptions)
function RadarChart(id, data, options) {
let cfg = {
w: 600,
h: 600,
margin: {
top: 20,
right: 20,
bottom: 20,
left: 20
},
levels: 3,
maxValue: 0,
labelFactor: 1.25,
wrapWidth: 60,
opacityArea: 0.35,
dotRadius: 4,
opacityCircles: 0.1,
strokeWidth: 2,
roundStroke: false,
color: ['#6678D9', '#61F06C', '#FF36AA']
}
if ('undefined' !== typeof options) {
for (var i in options) {
if ('undefined' !== typeof options[i]) {
cfg[i] = options[i];
}
} //for i
} //if
// 결론적으로 options에 들어가있는 녀석과 함수에있는 녀석을 매치시키는것
var maxValue =
// Math.max(cfg.maxValue,
d3.max(data,
(i) => d3.max(
i.map((o) => o.value)
// 벨류로 이루어진 어레이를 만들고, 그 어레이에서 최대값을 출력해낸다.
)
)
// )
var allAxis = data[0].map((d) => {
return d.axis
})
var total = allAxis.length,
radius = Math.min(cfg.w / 2, cfg.h / 2),
Format = d3.format('%'),
angleSlice = Math.PI * 2 / total
var rscale = d3.scaleLinear()
.range([0, radius])
.domain([0, maxValue])
d3.select(id).select('svg').remove()
var svg = d3.select(id).append('svg')
.attr('width', cfg.w + cfg.margin.left + cfg.margin.right)
.attr('height', cfg.h + cfg.margin.top + cfg.margin.bottom)
.attr('class',
'radar' + id);
var g = svg.append('g').attr('transform', `translate(${cfg.w/2+cfg.margin.left},${cfg.h/2+cfg.margin.top})`)
var filter = g.append('defs').append('filter').attr('id', 'glow')
var feGaussianBlur = filter.append('feGaussianBlur').attr('stdDeviation', '2.5')
.attr('result', 'coloredBlur')
var feMerge = filter.append('feMerge')
var feMergeNode1 = feMerge.append('feMergeNode').attr('in', 'coloredBlur')
var feMergeNode2 = feMerge.append('feMergeNode').attr('in', 'SourceGraphic')
var axisGrid = g.append('g').attr('class', 'axisWrapper')
axisGrid.selectAll('.levels').data(
d3.range(1, (cfg.levels + 1))
)
.join('circle')
.attr('class', 'gridCircle')
.attr('r', (d) => {
return radius / cfg.levels * d
})
.style('fill', '#CDCDCD')
.style('stroke', '#CDCDCD')
.style('fill-opacity', cfg.opacityCircles)
.style('filter', 'url(#glow)')
axisGrid.selectAll('.axisLabel')
.data(d3.range(1, cfg.levels + 1))
.join('text')
.attr('class', 'axisLabel')
.attr('x', 4)
.attr('y', (d) => {
return -d * radius / cfg.levels
})
.attr('dy', '0.4em')
.style('font-size', '10px')
.attr('fill', '#737373')
.text((d, i) => {
return Format(maxValue * d / cfg.levels)
})
var axis = axisGrid.selectAll('.axis')
.data(allAxis)
.join('g')
.attr('class', 'axis')
axis.append('line')
.attr('x1', 0)
.attr('y1', 0)
.attr("x2", function(d, i) {
return rscale(maxValue * 1.1) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("y2", function(d, i) {
return rscale(maxValue * 1.1) * Math.sin(angleSlice * i - Math.PI / 2);
})
.attr("class", "line")
.style("stroke", "white")
.style("stroke-width", "2px");
axis.append("text")
.attr("class", "legend")
.style("font-size", "11px")
.attr("text-anchor", "middle")
.attr("dy", "0.35em")
.attr("x", function(d, i) {
return rscale(maxValue * cfg.labelFactor) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("y", function(d, i) {
return rscale(maxValue * cfg.labelFactor) * Math.sin(angleSlice * i - Math.PI / 2);
})
.text(function(d) {
return d
})
.call(wrap, cfg.wrapWidth)
// .call(testfunc, 'testString')
var radarLine = d3.radialLine()
.radius(function(d) {
return rscale(d.value)
})
.angle(function(d, i, t) {
return i * angleSlice
})
.curve(d3.curveCardinalClosed)
// if (cfg.roundStrokes) {
// radarLine.interpolate("cardinal-closed");
// }
const blobWrapper = g.selectAll(".radarWrapper")
.data(data)
.join('g')
.attr("class", "radarWrapper");
blobWrapper
.append("path")
.attr("class", "radarArea")
.attr("d", function(d) {
return radarLine(d)
})
.attr('class', function(d, i) {
return `radar${i}`
})
.style("fill", (d, i) => cfg.color(i))
.style("fill-opacity", cfg.opacityArea)
.on('mouseover', function(d, i) {
d3.selectAll('.radarArea').transition().duration(200)
.style('fill-opacity', 0.1)
d3.select(this).transition().duration(200).style('fill-opacity', cfg.opacityArea);
})
.on('mouseout', function() {
//Bring back all blobs
d3.selectAll(".radarArea")
.transition().duration(200)
.style("fill-opacity", cfg.opacityArea);
});
blobWrapper.append('path')
.attr('class', 'radarStroke')
.attr('d', radarLine)
.attr('class', function(d, i) {
return `radarStroke${i}`
})
.style('stroke', (d, i) => cfg.color(i))
.style('stroke-width', cfg.strokeWidth + 'px')
.style('fill', 'none')
.style('filter', 'url(#glow)')
let round = 0;
blobWrapper.selectAll(".radarCircle")
.data(data)
.data(function(d) {
return d
})
.join('circle')
.attr("class", "radarCircle")
.attr("r", cfg.dotRadius)
.attr("cx", function(d, i) {
return rscale(d.value) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("cy", function(d, i) {
return rscale(d.value) * Math.sin(angleSlice * i - Math.PI / 2);
})
.style("fill", function(d, i, j) {
if (i == 0) {
round = round + 1;;
}
return cfg.color(round - 1);
})
.style("fill-opacity", 0.8);
var blobCircleWrapper = g.selectAll('.radarCircleWrapper')
.data(data)
.join('g')
.attr('class', 'radarCircleWrapper')
blobCircleWrapper.selectAll('circles').data(function(d) {
return d
})
.join('circle').attr('class', 'invisibleCircles')
.attr('r', cfg.dotRadius * 1.5)
.attr("cx", function(d, i) {
return rscale(d.value) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("cy", function(d, i) {
return rscale(d.value) * Math.sin(angleSlice * i - Math.PI / 2);
})
.style('fill', 'none')
.style('pointer-events', 'all')
.on('mouseover', function(d, i) {
newX = parseFloat(d3.select(this).attr('cx')) - 10;
newY = parseFloat(d3.select(this).attr('cy')) - 10;
tooltip
.attr('x', newX)
.attr('y', newY)
.text(Format(d.value))
.transition().duration(200)
.style('opacity', 1);
})
.on("mouseout", function() {
tooltip.transition().duration(200)
.style("opacity", 0);
});
var tooltip = g.append("text")
.attr("class", "tooltip")
.style("opacity", 0);
d3.selectAll('.btn').on('click', function(d) {
let selectedone = d3.select(this).attr('class').split(' ')[1]
console.log(selectedone);
d3.select(`.radar${selectedone}`)
.transition()
.duration(1000)
.attrTween('d', shrinkRadar(0))
function shrinkRadar(target) {
return function(d) {
var interps = d.map(da => d3.interpolate(da.value, target));
return function(t) {
d.forEach((da, i) => {
da.value = interps[i](t);
});
return radarLine(d)
}
}
}
// .attrTween('d',shrinkRadar(finalvalue))
})
}
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.4, // ems
y = text.attr("y"),
x = text.attr("x"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
.radarChart {
width: 1000px;
height: 500px;
}
.buttonwrap {
margin-left: 200px;
text-align: left;
/* border: dashed grey 0.5px; */
}
.btn {
text-align: center;
display: inline-block;
width: 100px;
height: 20px;
border: solid 1px navy;
border-radius: 5px;
margin: auto;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.3.1/d3.min.js"></script>
<svg class="radarChart"></svg>
<div class="buttonwrap">
<div class="btn 0">Show 1</div>
<div class="btn 1">Show 2</div>
<div class="btn 2">show 3</div>
</div>

D3 js - is it possible to draw straight lines between tree nodes?

I have created a horizontal tree diagram as shown in below image. I want straight lines between nodes. The curved lines between nodes are default in d3 js. I saw some answers on google for this but did not found any satisfactory result. So is it possible to draw straight lines between nodes in d3 js? If yes then how can I do that?
enter image description here
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous">
<script src="https://d3js.org/d3.v3.min.js"></script>
<style>
.node circle {
fill: #ff9900;
stroke: #ff9900;
stroke-width: 1px;
}
.node text {
font: 16px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
</style>
</head>
<body>
<script language="javascript">
var treeData = [{
"name": "1",
"parent": "null",
"children": [{
"name": "2",
"parent": "Persons",
"children": [{
"name": "3",
"parent": "Country of residence"
}, {
"name": "4",
"parent": "Country of residence"
}, {
"name": "5",
"parent": "Country of residence"
}, {
"name": "6",
"parent": "Country of residence"
}]
}]
}];
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0;
var tree = d3.layout.tree().size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var line = d3.svg.line()
.x(function(d) {
return d.lx;
})
.y(function(d) {
return d.ly;
});
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
update(root);
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Declare the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter the nodes.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeEnter.append("circle")
.attr("r", 40)
.style("fill", "#ff9900");
// append icon inside circle
nodeEnter.append("image")
.attr("xlink:href", "http://localhost/d3/user2.jpg")
.attr("x", "-18px")
.attr("y", "-18px")
.attr("width", "35px")
.attr("height", "35px");
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -40 : -50;
})
.attr("y", function(d) {
return d.children || d._children ? 55 : 55;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "start" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1);
// Declare the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", diagonal);
}
</script>
</body>
</body>
</html>
Define your line :
var line = d3.svg.line()
.x(function(d) {
return d.y; // because tree is horizontal
})
.y(function(d) {
return d.x; // because tree is horizontal
});
Change your links function to this because d3.svg.line() takes array of points as argument
Hope this helps
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
return line([d.source, d.target]);
});
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous">
<script src="https://d3js.org/d3.v3.min.js"></script>
<style>
.node circle {
fill: #ff9900;
stroke: #ff9900;
stroke-width: 1px;
}
.node text {
font: 16px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
</style>
</head>
<body>
<script language="javascript">
var treeData = [{
"name": "1",
"parent": "null",
"children": [{
"name": "2",
"parent": "Persons",
"children": [{
"name": "3",
"parent": "Country of residence"
}, {
"name": "4",
"parent": "Country of residence"
}, {
"name": "5",
"parent": "Country of residence"
}, {
"name": "6",
"parent": "Country of residence"
}]
}]
}];
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0;
var tree = d3.layout.tree().size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var line = d3.svg.line()
.x(function(d) {
return d.y;
})
.y(function(d) {
return d.x;
});
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
update(root);
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Declare the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter the nodes.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeEnter.append("circle")
.attr("r", 40)
.style("fill", "#ff9900");
// append icon inside circle
nodeEnter.append("image")
.attr("xlink:href", "http://localhost/d3/user2.jpg")
.attr("x", "-18px")
.attr("y", "-18px")
.attr("width", "35px")
.attr("height", "35px");
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -40 : -50;
})
.attr("y", function(d) {
return d.children || d._children ? 55 : 55;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "start" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1);
// Declare the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) { return line([d.source, d.target])});
}
</script>
</body>
</body>
</html>

Resources