d3js I can't render the links - javascript

Thanks for reading. I am new to using the d3js library. I want to generate directed and ordered graphs on the x-axis and y-axis. For this I start from a json format where I specify the nodes and their location, as well as the links between them as you can see in the code.
However, I am not getting the links between the nodes rendered based on the json. Any help?
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>[D3] Force + Drag + Zoom</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">
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<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-opacity: 1;
}
line {
stroke: rgb(212, 212, 212);
stroke-width: 1px;
shape-rendering: crispEdges;
}
.overlay {
fill: none;
pointer-events: all;
}
.link{
stroke: #ccc;
stroke-width: 4px;
}
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>
var width = 960,
height = 500,
resolution = 150,
r = 15;
var graph = {
"nodes": [
{"task": "1", "x": "150", "y": "450"},
{"task": "2", "x": "300", "y": "150"},
{"task": "3", "x": "450", "y": "300"}
],
"links": [
{"source": "1", "target": "2", "value": 3},
{"source": "2", "target": "3", "value": 3},
{"source": "1", "target": "3", "value": 3}
]
}
var margin = {
top: -5,
right: -5,
bottom: -5,
left: -5};
var color = d3.scale.category20();
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);
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.append("g");
var container = svg.append("g");
//force.links(graph.links)
// .start();
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; });
var link = container.append("g")
.attr('class', 'link')
.selectAll('.link')
.data(graph.links)
.enter().append('line')
.attr("marker-end", "url(#end)")
.style("stroke-width", function(d) {
return Math.sqrt(d.value);
});
var node = container.append("g")
.attr("class", "node")
.selectAll(".node")
.data(graph.nodes)
.enter().append('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', r)
.call(drag);
node.append("circle")
.attr("r", function (d) {
return d.weight * 2 + 12;
})
.style("fill", function (d) { return color(1 / d.task); });
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 + ")";
});*/
});
var linkedByIndex = {};
graph.links.forEach(function (d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index];
}
node.on("mouseover", function (d) {
node.classed("node-active", function (o) {
thisOpacity = isConnected(d, o) ? true : false;
this.setAttribute('fill-opacity', thisOpacity);
return thisOpacity;
});
link.classed("link-active", function (o) {
return o.source === d || o.target === d ? true : false;
});
d3.select(this).classed("node-active", true);
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", (d.weight * 2 + 12) * 1.5);
})
.on("mouseout", function (d) {
node.classed("node-active", false);
link.classed("link-active", false);
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", d.weight * 2 + 12);
});
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);
}//
function round(p, n) {
return p % n < n / 2 ? p - (p % n) : p + n - (p % n);
}
</script>
</body>

The links between your nodes are not showing up because you didn't specify the x1, x2, y1, and y2 attributes, which are needed to display a line.
There is also a circle that's created incorrectly within a circle, and its attribute r is not correctly defined. The program throws:
Error: attribute r: Expected length, "NaN".
I'm not sure what that is for, so I commented it out. The following code should display the links now:
Update: Now the links will be displayed correctly when you drag!
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>[D3] Force + Drag + Zoom</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">
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<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-opacity: 1;
}
line {
stroke: rgb(212, 212, 212);
stroke-width: 1px;
shape-rendering: crispEdges;
}
.overlay {
fill: none;
pointer-events: all;
}
.link{
stroke: #ccc;
stroke-width: 4px;
}
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>
var width = 960,
height = 500,
resolution = 150,
r = 15;
var graph = {
"nodes": [
{"task": "1", "x": "150", "y": "450"},
{"task": "2", "x": "300", "y": "150"},
{"task": "3", "x": "450", "y": "300"}
],
"links": [
{"source": "1", "target": "2", "value": 3},
{"source": "2", "target": "3", "value": 3},
{"source": "1", "target": "3", "value": 3}
]
}
var margin = {
top: -5,
right: -5,
bottom: -5,
left: -5
};
var color = d3.scale.category20();
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);
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.append("g");
var container = svg.append("g");
//force.links(graph.links)
// .start();
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; });
var link = container.append("g")
.attr('class', 'link')
.selectAll('.link')
.data(graph.links)
.enter().append('line')
.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;
}
})
.attr("marker-end", "url(#end)")
.style("stroke-width", function(d) {
return Math.sqrt(d.value);
});
var node = container.append("g")
.attr("class", "node")
.selectAll(".node")
.data(graph.nodes)
.enter().append('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', r)
.call(drag);
/*
node.append("circle")
.attr("r", function (d) {
return d.weight * 2 + 12;
})
.style("fill", function (d) { return color(1 / d.task); });
*/ // I'm not sure what this part is for
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 + ")";
});*/
});
var linkedByIndex = {};
graph.links.forEach(function (d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index];
}
node.on("mouseover", function (d) {
node.classed("node-active", function (o) {
thisOpacity = isConnected(d, o) ? true : false;
this.setAttribute('fill-opacity', thisOpacity);
return thisOpacity;
});
link.classed("link-active", function (o) {
return o.source === d || o.target === d ? true : false;
});
d3.select(this).classed("node-active", true);
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", (d.weight * 2 + 12) * 1.5);
})
.on("mouseout", function (d) {
node.classed("node-active", false);
link.classed("link-active", false);
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", d.weight * 2 + 12);
});
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.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);
}
</script>
</body>

Related

D3 how to exclude links from Gooey effect

Problem: The Gooey effect applies to the links too. Which creates a teardrop shaped frame instead of an circle.
The snipped contains a dragged() function which allows the user to tear off node 1 from node 0. Further it is possible to connect node 1 with node 0 again with the help of dragging. The code isn´t clean at all, as its a playground only.
Goal: How can I exclude the links from the Gooey effect in a way, that all links are displayed correctly and still achieve a proper circled shape. The shape of the Gooey effect can be manipulated by changing the -5 to -40, unfortunately it will hide the links completely:
.attr("values", "1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 50 -5")
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3v6 Playground</title>
<!-- call external d3.js framework -->
<script src="https://d3js.org/d3.v6.js"></script>
</head>
<style>
body {
overflow: hidden;
margin: 0px;
}
.canvas {
background-color: rgb(220, 220, 220);
}
.node {
cursor: pointer;
}
.node:hover {
stroke: red
}
.link {
fill: none;
cursor: default;
stroke: rgb(0, 0, 0);
stroke-width: 3px;
}
</style>
<body>
<svg id="svg"> </svg>
<script>
var graph = {
"nodes": [
{
"id": 0,
},
{
"id": 1,
},
{
"id": 2,
}
],
"links": [
{
"source": 1,
"target": 0,
},
{
"source": 2,
"target": 0,
},
]
}
var width = window.innerWidth
var height = window.innerHeight
var svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
.style("filter", "url(#gooey)")
// remove zoom on dblclick listener
d3.select("svg").on("dblclick.zoom", null)
var linkContainer = svg.append("g").attr("class", "linkContainer")
var nodeContainer = svg.append("g").attr("class", "nodeContainer")
var isSpliced = false;
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(150))
.force("charge", d3.forceManyBody().strength(-50))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collision", d3.forceCollide().radius(30))
//###############################################
//######### SVG Filter for Gooey effect #########
//###############################################
var defs = svg.append("defs");
var filter = defs.append("filter").attr("id", "gooey");
filter.append("feGaussianBlur")
.attr("in", "SourceGraphic")
.attr("stdDeviation", "10")
.attr("result", "blur");
filter.append("feColorMatrix")
.attr("in", "blur")
.attr("mode", "matrix")
.attr("values", "1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 50 -5")
.attr("result", "gooey");
filter.append("feComposite")
.attr("in", "SourceGraphic")
.attr("in2", "gooey")
.attr("operator", "atop");
initialize()
//###############################################
//############## Initialization #################
//###############################################
function initialize() {
link = linkContainer.selectAll(".link")
.data(graph.links)
.join("line")
.attr("class", "link")
node = nodeContainer.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)
)
node.selectAll("circle")
.data(d => [d])
.join("circle")
.attr("r", 30)
.style("fill", "white")
node.selectAll("text")
.data(d => [d])
.join("text")
.style("class", "icon")
.attr("font-family", "FontAwesome")
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle")
.attr("font-size", 30)
.attr("fill", "black")
.attr("stroke-width", "0px")
.attr("pointer-events", "none")
.text((d) => {
return d.id
})
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation
.force("link")
.links(graph.links)
}
//###############################################
//############# Update Positions ################
//###############################################
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 + ")";
});
}
//###############################################
//################ Drag Nodes ###################
//###############################################
var lineX;
var lineY;
var isPlugged = true;
function dragStarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
lineX = d.x
lineY = d.y
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
var root = graph.nodes.find(element => (element.id === 0))
var distance = (root.x - d.x) * 2 + (root.y - d.y) * 2
if (isPlugged && d.id === 1) {
var indexOfLink = graph.links.findIndex(element => (element.source.id === d.id))
if (distance < 0) {
distance = -distance
}
if (distance > 1000) {
graph.links.splice(indexOfLink, 1)
isPlugged = false;
}
} else {
if (distance < 0) {
distance = -distance
}
if (distance < 20) {
graph.links.push({ source: d.id, target: root.id })
isPlugged = true;
}
}
initialize()
}
function dragEnded(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
link.filter((a) => {
return a.source.id === d.id
}).style("stroke", "black")
}
</script>
</body>
</html>
Just apply the style to nodeContainer instead of svg - see comments below:
var svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
// <--------- remove the style here
// remove zoom on dblclick listener
d3.select("svg").on("dblclick.zoom", null)
var linkContainer = svg.append("g").attr("class", "linkContainer")
var nodeContainer = svg.append("g").attr("class", "nodeContainer")
.style("filter", "url(#gooey)") // <---------- add the style here
Your code updated:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3v6 Playground</title>
<!-- call external d3.js framework -->
<script src="https://d3js.org/d3.v6.js"></script>
</head>
<style>
body {
overflow: hidden;
margin: 0px;
}
.canvas {
background-color: rgb(220, 220, 220);
}
.node {
cursor: pointer;
}
.node:hover {
stroke: red
}
.link {
fill: none;
cursor: default;
stroke: rgb(0, 0, 0);
stroke-width: 3px;
}
</style>
<body>
<svg id="svg"> </svg>
<script>
var graph = {
"nodes": [
{
"id": 0,
},
{
"id": 1,
},
{
"id": 2,
}
],
"links": [
{
"source": 1,
"target": 0,
},
{
"source": 2,
"target": 0,
},
]
}
var width = window.innerWidth
var height = window.innerHeight
var svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
// remove zoom on dblclick listener
d3.select("svg").on("dblclick.zoom", null)
var linkContainer = svg.append("g").attr("class", "linkContainer")
var nodeContainer = svg.append("g").attr("class", "nodeContainer")
.style("filter", "url(#gooey)")
var isSpliced = false;
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(150))
.force("charge", d3.forceManyBody().strength(-50))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collision", d3.forceCollide().radius(30))
//###############################################
//######### SVG Filter for Gooey effect #########
//###############################################
var defs = svg.append("defs");
var filter = defs.append("filter").attr("id", "gooey");
filter.append("feGaussianBlur")
.attr("in", "SourceGraphic")
.attr("stdDeviation", "10")
.attr("result", "blur");
filter.append("feColorMatrix")
.attr("in", "blur")
.attr("mode", "matrix")
.attr("values", "1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 50 -5")
.attr("result", "gooey");
filter.append("feComposite")
.attr("in", "SourceGraphic")
.attr("in2", "gooey")
.attr("operator", "atop");
initialize()
//###############################################
//############## Initialization #################
//###############################################
function initialize() {
link = linkContainer.selectAll(".link")
.data(graph.links)
.join("line")
.attr("class", "link")
node = nodeContainer.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)
)
node.selectAll("circle")
.data(d => [d])
.join("circle")
.attr("r", 30)
.style("fill", "white")
node.selectAll("text")
.data(d => [d])
.join("text")
.style("class", "icon")
.attr("font-family", "FontAwesome")
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle")
.attr("font-size", 30)
.attr("fill", "black")
.attr("stroke-width", "0px")
.attr("pointer-events", "none")
.text((d) => {
return d.id
})
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation
.force("link")
.links(graph.links)
}
//###############################################
//############# Update Positions ################
//###############################################
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 + ")";
});
}
//###############################################
//################ Drag Nodes ###################
//###############################################
var lineX;
var lineY;
var isPlugged = true;
function dragStarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
lineX = d.x
lineY = d.y
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
var root = graph.nodes.find(element => (element.id === 0))
var distance = (root.x - d.x) * 2 + (root.y - d.y) * 2
if (isPlugged && d.id === 1) {
var indexOfLink = graph.links.findIndex(element => (element.source.id === d.id))
if (distance < 0) {
distance = -distance
}
if (distance > 1000) {
graph.links.splice(indexOfLink, 1)
isPlugged = false;
}
} else {
if (distance < 0) {
distance = -distance
}
if (distance < 20) {
graph.links.push({ source: d.id, target: root.id })
isPlugged = true;
}
}
initialize()
}
function dragEnded(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
link.filter((a) => {
return a.source.id === d.id
}).style("stroke", "black")
}
</script>
</body>
</html>

D3 linkText appends instead updating existing value

Recently I migrated my graph to v6 where I replaced the enter(), remove(), exit() pattern with join(). Unfortunately, I can´t change the link text any longer. Usually, the text should switch between "blue" and "green" as soon as a link was clicked.
I know, that I append a new text object instead of updating the existing value. Which is the problem. It's visible in the browser inspector. But I can´t figure out how to adapt the code, as join() is not working at this point.
Any hints?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3 JOIN Test</title>
<!-- call external d3.js framework -->
<script src="https://d3js.org/d3.v6.js"></script>
<!-- import multiselection framework -->
<script src="https://d3js.org/d3-selection-multi.v1.js"></script>
</head>
<style>
body {
overflow: hidden;
margin: 0px;
}
.canvas {
background-color: rgb(220, 220, 220);
}
.link {
cursor: pointer;
stroke: rgb(0, 0, 0);
stroke-width: 4px;
}
.link:hover {
stroke: red;
}
</style>
<body>
<svg id="svg"> </svg>
<script>
var graph = {
"nodes": [
{
"id": 1,
},
{
"id": 2,
},
{
"id": 3,
}
],
"links": [
{
"source": 1,
"target": 2,
"type": "blue"
},
{
"source": 2,
"target": 3,
"type": "blue"
},
{
"source": 3,
"target": 1,
"type": "blue"
}
]
}
// declare initial variables
var svg = d3.select("svg")
width = window.innerWidth
height = window.innerHeight
// define cavnas area to draw everything
svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
// remove zoom on dblclick listener
d3.select("svg").on("dblclick.zoom", null)
var linksContainer = svg.append("g").attr("class", "linksContainer")
var nodesContainer = svg.append("g").attr("class", "nodesContainer")
// iniital force simulation
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(200))
.force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("attraceForce", d3.forceManyBody().strength(70));
initialze()
function initialze() {
links = linksContainer.selectAll(".link")
.data(graph.links)
.join("line")
.attr("class", "link")
.on("click", click)
linkPaths = linksContainer.selectAll(".linkPath")
.data(graph.links)
.join("path")
.style("pointer-events", "none")
.attrs({
"class": "linkPath",
"fill-opacity": 1,
"stroke-opacity": 1,
"id": function (d, i) { return "linkPath" + i }
})
.style("display", "block")
linkLabels = linksContainer.selectAll(".linkLabel")
.data(graph.links)
.join("text")
.style("pointer-events", "none")
.attrs({
"class": "linkLabel",
"id": function (d, i) { return "linkLabel" + i },
"font-size": 16,
"fill": "black"
})
.style("display", "block")
linkLabels
.append("textPath")
.attr('xlink:href', function (d, i) { return '#linkPath' + i })
.style("text-anchor", "middle")
.style("pointer-events", "none")
.attr("startOffset", "50%")
.text(function (d) { return d.type })
nodes = nodesContainer.selectAll(".node")
.data(graph.nodes, d => d.id)
.join("circle")
.attr("class", "node")
.attr("r", 30)
.attr("fill", "whitesmoke")
.attr("stroke", "white")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation
.force("link")
.links(graph.links)
}
function mouseEnter(event, d) {
d3.select(this).style("fill", "lightblue")
}
function mouseLeave(event, d) {
d3.select(this).style("fill", "whitesmoke")
}
function click(event, d) {
if (d.type == "blue") {
d.type = "green"
} else if (d.type == "green") {
d.tyoe = "blue"
}
initialze()
}
function close() {
contextMenuLink.classList.remove("active")
}
function ticked() {
links
.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;
});
nodes
.attr("transform", function (d) {
return "translate(" + d.x + ", " + d.y + ")";
});
linkPaths.attr('d', function (d) {
return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
});
linkLabels.attr('transform', function (d) {
if (d.target.x < d.source.x) {
var bbox = this.getBBox();
rx = bbox.x + bbox.width / 2;
ry = bbox.y + bbox.height / 2;
return 'rotate(180 ' + rx + ' ' + ry + ')';
}
else {
return 'rotate(0)';
}
});
}
function dragStarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
}
</script>
</body>
</html>
For those who face a similar problem. I played around a bit and got it done by adding an empty .text("") attribute to the ".linklabel" class. The correct version looks liks:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3 JOIN Test</title>
<!-- call external d3.js framework -->
<script src="https://d3js.org/d3.v6.js"></script>
</head>
<style>
body {
overflow: hidden;
margin: 0px;
}
.canvas {
background-color: rgb(220, 220, 220);
}
.link {
cursor: pointer;
stroke: rgb(0, 0, 0);
stroke-width: 4px;
}
.link:hover {
stroke: red;
}
</style>
<body>
<svg id="svg"> </svg>
<script>
var graph = {
"nodes": [
{
"id": 1,
},
{
"id": 2,
},
{
"id": 3,
}
],
"links": [
{
"source": 1,
"target": 2,
"type": "blue"
},
{
"source": 2,
"target": 3,
"type": "blue"
},
{
"source": 3,
"target": 1,
"type": "blue"
}
]
}
// declare initial variables
var svg = d3.select("svg")
width = window.innerWidth
height = window.innerHeight
// define cavnas area to draw everything
svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
// remove zoom on dblclick listener
d3.select("svg").on("dblclick.zoom", null)
var linksContainer = svg.append("g").attr("class", "linksContainer")
var nodesContainer = svg.append("g").attr("class", "nodesContainer")
// iniital force simulation
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(200))
.force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("attraceForce", d3.forceManyBody().strength(70));
initialze()
function initialze() {
links = linksContainer.selectAll(".link")
.data(graph.links)
.join("line")
.attr("class", "link")
.on("click", click)
linkPaths = linksContainer.selectAll(".linkPath")
.data(graph.links)
.join("path")
.attr("class", "linkPath")
.attr("id", function (d, i) { return "linkPath" + i })
linkLabels = linksContainer.selectAll(".linkLabel")
.data(graph.links)
.join("text")
.attr("class", "linkLabel")
.attr("id", function (d, i) { return "linkLabel" + i })
.attr("font-size", 16)
.attr("fill", "black")
.text("")
linkLabels
.append("textPath")
.attr('xlink:href', function (d, i) { return '#linkPath' + i })
.style("text-anchor", "middle")
.attr("startOffset", "50%")
.text(function (d) { return d.type })
nodes = nodesContainer.selectAll(".node")
.data(graph.nodes, d => d.id)
.join("circle")
.attr("class", "node")
.attr("r", 30)
.attr("fill", "whitesmoke")
.attr("stroke", "white")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation
.force("link")
.links(graph.links)
}
function click(event, d) {
if (d.type == "blue") {
d.type = "green"
} else if (d.type == "green") {
d.type = "blue"
}
initialze()
}
function ticked() {
links
.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;
});
nodes
.attr("transform", function (d) {
return "translate(" + d.x + ", " + d.y + ")";
});
linkPaths.attr('d', function (d) {
return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
});
linkLabels.attr('transform', function (d) {
if (d.target.x < d.source.x) {
var bbox = this.getBBox();
rx = bbox.x + bbox.width / 2;
ry = bbox.y + bbox.height / 2;
return 'rotate(180 ' + rx + ' ' + ry + ')';
}
else {
return 'rotate(0)';
}
});
}
function dragStarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
}
</script>
</body>
</html>

D3.js - plotting images with nested array in json

I am a newbie in d3, the following is my input json file,
{
"y_axis_list":["A","B","C","D","E","F","G"],
"Points":[
{
"y": "A",
"x": 10,
"Persona":["link1"]
},
{
"y" : "B",
"x": 20,
"Persona":["link2","link1"]
},
{
"y" : "C",
"x": 30,
"Persona":["link2","link3"]
},
{
"y" : "D",
"x": 40,
"Persona":["link2","link3"]
},
{
"y" : "E",
"x" : 50,
"Persona":["link5","link6"]
},
{
"y" : "F",
"x" : 60,
"Persona":["link7","link8"]
},
{
"y" : "G",
"x" : 70,
"Persona":["link9","link10"]
}
]
}
the following is my code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>D3 v4 - linechart</title>
<style>
#graph {
width: 900px;
height: 500px;
}
.tick line {
stroke-dasharray: 2 2 ;
stroke: #ccc;
}
.x{
display: none;
}
.y path{
fill: none;
stroke: none;
/* set the CSS */
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
</head>
<body>
<div id="graph"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
!(function(){
"use strict"
var width,height
var chartWidth, chartHeight
var margin
var svg = d3.select("#graph").append("svg")
var axisLayer = svg.append("g").classed("axisLayer", true)
var chartLayer = svg.append("g").classed("chartLayer", true)
var xScale = d3.scaleLinear()
var yScale = d3.scalePoint()
var align = 0
var i=10;
//d3.tsv("data1.tsv", cast, main)
d3.json("http://localhost/newest.json",cast)
//データの方変換
function cast(datafull) {
console.log("got it");
var data=datafull["Points"];enter code here
data.forEach(function(data) {
console.log(data.y);
data.y = data.y;
data.x = +data.x;
});
main(data,datafull);
}
function main(data,datafull) {
console.log("in main");
setSize(data,datafull)
drawAxis()
drawChart(data,datafull)
}
function setSize(data,datafull) {
width = document.querySelector("#graph").clientWidth
height = document.querySelector("#graph").clientHeight
margin = {top:40, left:100, bottom:40, right:0 }
chartWidth = width - (margin.left+margin.right+8)
chartHeight = height - (margin.top+margin.bottom)
svg.attr("width", width).attr("height", height)
axisLayer.attr("width", width).attr("height", height)
chartLayer
.attr("width", chartWidth)
.attr("height", chartHeight)
.attr("transform", "translate("+[margin.left, margin.top]+")")
xScale.domain([0, d3.max(data, function(d) { return d.x; })]).range([0,chartWidth])
yScale.domain(datafull["y_axis_list"]).range([chartHeight, 0]).align(1)
}
function drawChart(data,datafull) {
console.log("in drawChart");
var t = d3.transition()
.duration(4000)
.ease(d3.easeLinear)
.on("start", function(d){ console.log("transiton start") })
.on("end", function(d){ console.log("transiton end") });
// var tooltip = d3.tip().attr('class', 'd3-tip').html(function(d) { return 10; });
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var lineGen = d3.line()
.x(function(d) { i=i+50;console.log(i); return xScale(d.x); })
.y(function(d) { return yScale(d.y) })
.curve(d3.curveStepAfter)
var line = chartLayer.selectAll(".line")
.data([data])
line.enter().append("path").classed("line", true)
.merge(line)
.attr("d", lineGen)
.attr("fill", "none")
.attr("stroke", "blue")
.attr("stroke-width","2px")
.attr("stroke-dasharray", function(d){ return this.getTotalLength() })
.attr("stroke-dashoffset", function(d){ return this.getTotalLength() })
chartLayer.selectAll(".line").transition(t)
.attr("stroke-dashoffset", 0)
var img = chartLayer.selectAll('image')
.data(data)
.enter()
.append('image')
.attr("class","logo")
.attr("xlink:href", "http://localhost/whatsapp-logo.jpg" )
.attr("x", function(d,i){ return xScale(d.x+5/2) })
.attr("y", function(d,i){ return yScale(d.y) })
.attr("width", 16)
.attr("height", 16)
.style("opacity",0);
img.transition().delay(4000).duration(500).ease(d3.easeLinear).style("opacity",1);
img.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html("Persona people" + "<br/>" + d.x)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
chartLayer.selectAll("circle").classed("circle",true)
.data(data)
.enter().append("circle")
.attr("class", "circle")
.attr("fill","none")
.attr("stroke","black")
.attr("cx", function(d) { return xScale(d.x); })
.attr("cy", function(d) { return yScale(d.y); })
.attr("r", 4)
}
function drawAxis(){
var yAxis = d3.axisLeft(yScale)
.tickSizeInner(-chartWidth)
axisLayer.append("g")
.attr("transform", "translate("+[margin.left, margin.top]+")")
.attr("class", "axis y")
.call(yAxis);
var xAxis = d3.axisBottom(xScale)
axisLayer.append("g")
.attr("class", "axis x")
.attr("transform", "translate("+[margin.left, chartHeight+margin.top]+")")
.call(xAxis);
}
function update(){
}
}());
</script>
</body>
</html>
I want the images given in the persona list to appear in the same line as that of the y coordinate in the json with x-axis changing minutely. The links are the links to images which are to be loaded.
I went through a lot of questions, but I was not able to figure out the method to do that.
My current output is given below.
Now there is one image in each line, I want the number of images in persona list (input json) to appear in each of the corresponding lines.
Rewrite your img variable this way (pay attention to the comments):
var img = chartLayer
.selectAll('g')
.data(data)
.enter()
.append('g') // append g element, here we will be append image as child
.attr("transform", function(d, i) {
var x = xScale(d.x + 5 / 2);
var y = yScale(d.y);
return 'translate(' + x + ', ' + y + ')'
})
.selectAll('image')
.data(function(d) { // here we format data as we need
return d.Persona.map(function(icon, index) {
return {
icon: icon,
tooltipText: d.Text[index] // set tooltip text
}
})
})
.enter()
.append('image') // append image element
.attr("class", "logo")
.attr("xlink:href", function(d) { // set appropriate href attribute
return d.icon;
})
.attr("x", function(d, i) { // move image by index
return 20 * i;
})
.attr("width", 16)
.attr("height", 16)
.style("opacity", 0);
...
img.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html("Persona people" + "<br/>" + d.tooltipText) // get tooltip text
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
...
Look at the working example - https://jsfiddle.net/levsha/og92k0gv/

Displaying total value for each item in a chord chart

I was working on the chord diagram and I am unable to show the total value for each product. I want it to display the total count for each category when I hover over the radial arc for a particular category. The example is taken from http://bl.ocks.org/mbostock/4062006. Any prompt help would be greatly appreciated. Thank you :)
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.chord path {
fill-opacity: .67;
stroke: #000;
stroke-width: .5px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
// From http://mkweb.bcgsc.ca/circos/guide/tables/
var matrix = [
[11975, 5871, 8916, 2868],
[ 1951, 10048, 2060, 6171],
[ 8010, 16145, 8090, 8045],
[ 1013, 990, 940, 6907]
];
var chord = d3.layout.chord()
.padding(.05)
.sortSubgroups(d3.descending)
.matrix(matrix);
var width = 960,
height = 500,
innerRadius = Math.min(width, height) * .41,
outerRadius = innerRadius * 1.1;
var fill = d3.scale.ordinal()
.domain(d3.range(4))
.range(["#000000", "#FFDD89", "#957244", "#F26223"]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
svg.append("g").selectAll("path")
.data(chord.groups)
.enter().append("path")
.style("fill", function(d) { return fill(d.index); })
.style("stroke", function(d) { return fill(d.index); })
.attr("d", d3.svg.arc().innerRadius(innerRadius).outerRadius(outerRadius))
.on("mouseover", fade(.1))
.on("mouseout", fade(1));
var ticks = svg.append("g").selectAll("g")
.data(chord.groups)
.enter().append("g").selectAll("g")
.data(groupTicks)
.enter().append("g")
.attr("transform", function(d) {
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+ "translate(" + outerRadius + ",0)";
});
ticks.append("line")
.attr("x1", 1)
.attr("y1", 0)
.attr("x2", 5)
.attr("y2", 0)
.style("stroke", "#000");
ticks.append("text")
.attr("x", 8)
.attr("dy", ".35em")
.attr("transform", function(d) { return d.angle > Math.PI ? "rotate(180)translate(-16)" : null; })
.style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.text(function(d) { return d.label; });
svg.append("g")
.attr("class", "chord")
.selectAll("path")
.data(chord.chords)
.enter().append("path")
.attr("d", d3.svg.chord().radius(innerRadius))
.style("fill", function(d) { return fill(d.target.index); })
.style("opacity", 1)
.append("svg:title").text(function(d, i) { return "Value : " + d.source.value });;
// Returns an array of tick angles and labels, given a group.
function groupTicks(d) {
var k = (d.endAngle - d.startAngle) / d.value;
return d3.range(0, d.value, 1000).map(function(v, i) {
return {
angle: v * k + d.startAngle,
label: i % 5 ? null : v / 1000 + "k"
};
});
}
// Returns an event handler for fading a given chord group.
function fade(opacity) {
return function(g, i) {
svg.selectAll(".chord path")
.filter(function(d) { return d.source.index != i && d.target.index != i; })
.transition()
.style("opacity", opacity);
};
}
</script>
`

d3.js Maximum call stack size exceeded error

When I try to layout my force-directed graph, the following is the error I receive. I read about this issue in Mike Bostock's github page and found that it might be due to NaN values of coordinates or all the points drawn at the same point. I checked into the console and I found that all of my points are getting drawn at the same X and Y values.
On an example data, the code worked perfectly well but no more now. My data goes like 45-50 levels away from the center node. I have successfully made a tree layout with this data. Wanted to try the force directed layout but it did not work.
Any help regarding how to draw the nodes on separate coordinates would be much appreciated.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 10, right: 10, bottom: 20, left: 40},
width = 1300 - margin.left - margin.right,
height = 800 - margin.top - margin.bottom;
var color = d3.scale.category20();
var force = d3.layout.force().charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("body").append("svg:svg")
.attr("width", width)
.attr("height", height)
//.attr("pointer-events","all")
.append('svg:g')
//.call(d3.behavior.zoom().translate([100,50]).scale(.5).on("zoom",redraw))
.append('svg:g')
.attr("transform","translate(100,50)scale(.5,.5)");
svg.append('svg:rect')
.attr('width', width)
.attr('height', height)
.attr('fill','white')
.attr('opacity',0);
function redraw() {
var trans = d3.event.translate;
var scale = d3.event.scale;
svg.attr("transform",
"translate(" + trans + ")"
+ " scale(" + scale + ")");
};
d3.json("test_data.json", function(error, graph) {
var nodeMap = {};
graph.nodes.forEach(function(x) { nodeMap[x.name] = x; });
graph.links = graph.links.map(
function(x)
{
return {
source: nodeMap[x.source],
target: nodeMap[x.target],
value: x.value
};
});
console.log(graph.nodes);
console.log(graph.links);
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke", function(d) { return color(d.value); })
.style("stroke-width", function(d) {
//console.log(d.value);
return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.value); })
.call(force.drag)
.on("mousedown",
function(d) {
d.fixed = true;
d3.select(this).classed("sticky", true);
}
)
.on("mouseover",fade(0.1))
.on("mouseout",fade(1));
var linkedByIndex = {};
graph.links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
node.append("title")
.text(function(d) {
return "Name : "+d.name+"\n"+"Parent: "+d.parent +"\n"+"Relationship: "+ d.relationship +"\n"+ "Creation Date: "+ d.cod +"\n"; });
function fade(opacity)
{
return function(d) {
node.style("stroke-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
this.setAttribute('fill-opacity', thisOpacity);
return thisOpacity;
});
link.style("stroke-opacity", opacity).style("stroke-opacity", function(o) {
return o.source === d || o.target === d ? 1 : opacity;
});
};
}
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 + ")"; });
/*node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });*/
});
});
</script>
The JSON looks like:
{
"links": [
{
"source": "Me",
"target": "Adam",
"value": 10
},
{
"source": "Me",
"target": "You",
"value": 10
}
], "nodes": [
{
"ancestor": "Adam",
"cod": 19061964,
"distance": 0,
"name": "Adam",
"parent": null,
"relationship": null,
"value": 10
},
{
"ancestor": "Adam",
"cod": 13032003,
"distance": 1,
"name": "Me",
"parent": "You",
"relationship": "Father",
"value": 10
}
]
}
EDIT: I get the error in the following statement:
force
.nodes(graph.nodes)
.links(graph.links)
.start();
highlighting:
"start();"
In your data your links are to names as opposed to indices. Check out the example data from http://bl.ocks.org/mbostock/4062045.
Links have the form: {"source":1,"target":0,"value":1}, which points to the index of the node.

Categories