I am having force-directed graph. It shows nodes without problems and on console it writes links - source and target. But doesn't connect it to nodes. I can see there is no field for coordinators see picture
Whole code is in Kibana and more complicated but here is the core:
const link = svg.selectAll('link')
.data(links)
.enter()
.append('svg:line')
.attr('class', 'link')
.style("stroke-width", function (d) {return Math.sqrt(d.value);})
.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;});
force.on("tick", tick);
function tick() {
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 node = svg.selectAll('node')
.data(nodes)
.enter()
.append('circle')
.attr('class', 'node')
.style("opacity", .9)
.attr("r", function(d) { return 10; })
.attr("id", function(d) { return d.id; })
.attr("cy", function(d){return d.y;})
.attr("cx", function(d){return d.x;})
.style("fill", function(d) { return c20(d.value);})
.style("stroke-width", 20);
const svg = div.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate('+ width / 2 + ',' + height / 3 + ')');
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.charge(-150)
.linkDistance(90)
.start();
MY data structure:
links =
[{"source": 0, "target": 1, "value": 30},
{"source": 0, "target": 2, "value": 5},
{"source": 1, "target": 3, "value": 1},
{"source": 2, "target": 0, "value": 20}]
nodes =
[{"ip": "92.15.122.1", "value": 5, id: 0},
{"ip": "12.154.154.22", "value": 20, id: 1},
{"ip": "255.12.11.1", "value": 30, id: 2},
{"ip": "54.55.6.55", "value": 1, id: 3}]
I think the problem is connecting "id" from "nodes" to "source" and "target" in links. Any idea how?
You need to set the stroke colour on the link variable
var link = svg.selectAll('link')
.data(links)
.enter()
.append('line')
.attr('class', 'link')
.style("stroke", 'black')
See below the full snippet. I have cleaned it up to get it to run in a Stack snippet.
var width = 320,
height = 240;
links = [{
"source": 0,
"target": 1,
"value": 30
},
{
"source": 0,
"target": 2,
"value": 5
},
{
"source": 1,
"target": 3,
"value": 1
},
{
"source": 2,
"target": 0,
"value": 20
}
]
nodes = [{
"ip": "92.15.122.1",
"value": 5,
id: 0
},
{
"ip": "12.154.154.22",
"value": 20,
id: 1
},
{
"ip": "255.12.11.1",
"value": 30,
id: 2
},
{
"ip": "54.55.6.55",
"value": 1,
id: 3
}
]
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.attr('transform', 'translate(' + width / 2 + ',' + height / 3 + ')');
var force = d3.layout.force()
.size([width, height])
.nodes(nodes)
.links(links);
function tick() {
node.attr('r', width / 25)
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
});
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;
});
}
force.on("tick", tick);
var link = svg.selectAll('link')
.data(links)
.enter()
.append('line')
.attr('class', 'link')
.style("stroke", 'black')
.style("stroke-width", function(d) {
return Math.sqrt(d.value);
}).attr('x1', function(d) { return d.source.x;})
.attr('y1', function(d) { return d.source.y;})
.attr('x2', function(d) { return d.target.x;})
.attr('y2', function(d) { return d.target.y;});
var node = svg.selectAll('node')
.data(nodes)
.enter()
.append('circle')
.attr('class', 'node')
.style("opacity", .9)
.attr("r", function(d) {
return 10;
})
.attr("id", function(d) {
return d.id;
})
.attr("cy", function(d) {
return d.y;
})
.attr("cx", function(d) {
return d.x;
})
.style("stroke-width", 20);
force
.nodes(nodes)
.links(links)
.charge(-150)
.linkDistance(90)
.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Related
I am trying to make my tree graph respond based on the value that is selected in the input. I get it to work for the first selection but nothing happens when I put the selection back to "All" again.
I get the following error in the console:
I guess it is because when going to the selection some of the children are removed and thus not found anymore when putting it on the full selection but I am not sure how I could fix this.
See codepen
The code for the graph:
//Load data
const data = [
{
"name": "Top Level",
"parent": "null",
"value": 10,
"type": "black",
"level": "red",
"children": [
{
"name": "Level 2A",
"parent": "Top Level",
"value": 15,
"type": "grey",
"level": "red",
"children": [
{
"name": "Son of A",
"parent": "Level 2: A",
"value": 5,
"type": "steelblue",
"level": "orange"
},
{
"name": "Daughter of A",
"parent": "Level 2: A",
"value": 8,
"type": "steelblue",
"level": "red"
}
]
},
{
"name": "Level 2B",
"parent": "Top Level",
"value": 10,
"type": "grey",
"level": "green"
}
]
}
];
var margin = {top: 10, right: 10, bottom: 10, left: 50},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var tree = d3.cluster()
.size([height, width])
.size([height-margin.top-margin.bottom,width-margin.left-margin.right]);
//Create root
var root = d3.hierarchy(data[0], function(d) { return d.children;});
//Attach canvas element
var svg = d3.select("body")
.append("svg")
.attr("width", 1000)
.attr("height", 1000);
var g = svg
.append("g")
.attr('transform','translate('+ margin.left +','+ margin.right +')');
var link = g.selectAll(".link")
.data(tree(root).links())
.enter()
.append("path")
.attr("class", "link")
.attr("d", d3.linkHorizontal()
.x(function(d) {return d.y;})
.y(function(d) {return d.x;}));
var node = g.selectAll(".node")
.data(root.descendants())
.enter()
.append("g")
.attr("class", "link")
.attr("class", d =>
{ return "node" + (d.children ? " node--internal" : " node--leaf")})
.attr("transform", d =>
{ return "translate(" + d.y + ","+ d.x + ")" ; })
var text = g.selectAll("text")
.data(root.descendants())
.enter().append("text")
.text(d => d.data.name)
.attr("class", "label glow")
.attr('text-anchor', "start")
.attr("x", d => d.y)
.attr("y", d => d.x);
circle = node.append("circle")
.attr("r", 2.5)
//Select
var selections = ["All", "Level 2A", "Level 2B"]
d3.select("#selectButton")
.selectAll('myOptions')
.data(selections)
.enter()
.append('option')
.text(function (d) { return d; }) //
.attr("value", function (d) { return d; })
// SelectInput
d3.select("#selectButton").on("change", function(d){
selectedGroup = this.value
updateChart(selectedGroup, data)
})
//Update chart function
function updateChart(selection, data){
//Filter data
if(selection != "All"){
var selData = data[0].children.filter(d => d.name == selection);
} else {
var selData = data
}
console.log(selData)
var root = d3.hierarchy(selData[0], function(d) { return d.children;});
console.log(root)
//update chart
link
.data(tree(root).links())
.join()
.transition()
.duration(1000)
.attr("class", "link")
.attr("d", d3.linkHorizontal()
.x(function(d) {return d.y;})
.y(function(d) {return d.x;})
);
text
.data(root.descendants())
.join()
.transition()
.duration(1000)
.attr('text-anchor', "start")
.attr("x", d => d.y)
.attr("y", d => d.x);
node
.data(root.descendants())
.join()
.transition()
.duration(1000)
.attr("class", "link")
.attr("class", d =>
{ return "node" + (d.children ? " node--internal" : " node--leaf")})
.attr("transform", d =>
{ return "translate(" + d.y + ","+ d.x + ")" ; })
circle
.data(root.descendants())
.join()
.transition()
.duration(1000)
.attr("r", 2.5)
};
To answer my own question. I had to set what exactly needs to happen upon enter(), update() and exit().
I changed from just joining:
link
.data(tree(root).links())
.join()
.transition()
.duration(1000)
.attr("class", "link")
.attr("d", d3.linkHorizontal()
.x(function(d) {return d.y;})
.y(function(d) {return d.x;})
);
To:
var link = g
.selectAll(".link")
.data(tree(root).links())
.join(
(enter) =>
enter
.append("path")
.attr("class", "link")
.attr(
"d",
d3
.linkHorizontal()
.x(function (d) {
return d.y;
})
.y(function (d) {
return d.x;
})
),
(update) =>
update
.transition()
.duration(1000)
.attr("class", "link")
.attr(
"d",
d3
.linkHorizontal()
.x(function (d) {
return d.y;
})
.y(function (d) {
return d.x;
})
),
(exit) => exit.remove()
);
For the full working tree see:
https://codepen.io/nvelden/pen/jOxgoVj
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>
Below example should update the nodes after select the groups at the bottom. but the select works fine, the nodes on the svg not updated!
force_json()
function force_json(){
var svg = d3.select('body').append('svg')
.attr('width',200).attr('height',100)
.style('border','1px solid red'),
width = +svg.attr("width"),
height = +svg.attr("height");
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
var color = d3.scaleOrdinal(d3.schemeCategory10);
var jsonobj = {
"nodes": [
{"id": "Myriel", "group": 1},
{"id": "Napoleon", "group": 2},
{"id": "Mlle.Baptistine", "group": 3},
{"id": "Mme.Magloire", "group": 1},
],
"links": [
{"source": "Napoleon", "target": "Myriel", "value": 10},
{"source": "Mlle.Baptistine", "target": "Myriel", "value": 8},
{"source": "Mme.Magloire", "target": "Myriel", "value": 10},
]
}
// d3.json("miserables.json",function(error, graph) {
// if (error) throw error;
// });
process_data(jsonobj)
function process_data(graph) {
var currNodes = graph.nodes
var currLinks = graph.links
var nodesByGroup = d3.group(graph.nodes,d => d.group)
var catMenu = d3.select("body").append('div')
catMenu
.append("select")
.selectAll("option")
.data(nodesByGroup)
.enter()
.append("option")
.attr("value", function(d,i) {
return d[0];
})
.text(function(d,i){
return d[0];
})
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(currLinks)
.enter().append("line")
.attr('stroke','#aaa')
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(currNodes)
.enter().append("circle")
.attr("r", 5)
.attr('pointer-events','all')
.attr('stroke','none')
.attr('stroke-wdith',40)
.attr("fill", function(d) { return color(d.group);})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("title")
.text(function(d) { return d.id; });
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {return d.id;})
simulation
.nodes(currNodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
catMenu.on('change', function(){
var selectedGroup = +d3.select(this)
.select("select")
.property("value");
currNodes = filterNodes(selectedGroup);
});
function filterNodes(group) {
var filteredNodes = nodesByGroup.get(group)
return filteredNodes;
}
}
function dragstarted(event,d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event,d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event,d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
Re-render filtered nodes and links upon group selection:
force_json()
function force_json(){
var svg = d3.select('body').append('svg')
.attr('width',200).attr('height',200)
.style('border','1px solid red'),
width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory10);
var jsonobj = {
"nodes": [
{"id": "Myriel", "group": 1},
{"id": "Napoleon", "group": 2},
{"id": "Mlle.Baptistine", "group": 3},
{"id": "Mme.Magloire", "group": 1},
{"id": "A", "group": 2},
{"id": "B", "group": 3},
{"id": "C", "group": 3}
],
"links": [
{"source": "Napoleon", "target": "Myriel", "value": 10},
{"source": "Napoleon", "target": "A", "value": 10},
{"source": "Mlle.Baptistine", "target": "Myriel", "value": 8},
{"source": "Mlle.Baptistine", "target": "B", "value": 8},
{"source": "Mme.Magloire", "target": "Myriel", "value": 10},
{"source": "A", "target": "B", "value": 10},
{"source": "C", "target": "B", "value": 10},
]
}
process_data(jsonobj)
function process_data(graph) {
var nodesByGroup = d3.group(graph.nodes,d => d.group)
var catMenu = d3.select("body").append('div')
catMenu
.append("select")
.selectAll("option")
.data(nodesByGroup)
.enter()
.append("option")
.attr("value", function(d,i) {
return d[0];
})
.text(function(d,i){
return d[0];
})
catMenu.select('select').append('option').text('all').attr("selected", "selected");
const updateGraph = (nodes, links) => {
const simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
svg.selectAll('g').remove();
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr('stroke','#aaa')
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 5)
.attr('pointer-events','all')
.attr('stroke','none')
.attr('stroke-wdith',40)
.attr("fill", function(d) { return color(d.group);})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("title")
.text(d => d.id);
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {return d.id;})
simulation
.nodes(nodes)
.on("tick", ticked);
simulation.force("link")
.links(links);
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
function dragstarted(event,d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event,d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event,d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}
updateGraph(graph.nodes, graph.links);
catMenu.on('change', function(){
var selectedGroup = d3.select(this)
.select("select")
.property("value");
let nodes, links;
if (selectedGroup === 'all') {
nodes = graph.nodes;
links = graph.links;
} else {
const group = parseInt(selectedGroup);
nodes = graph.nodes.filter(n => n.group === group);
console.log(graph.links)
links = graph.links.filter(link => {
const source = nodes.find(n => n.id === link.source.id);
const target = nodes.find(n => n.id === link.target.id);
return source && target;
})
}
updateGraph(nodes, links);
});
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
I am trying to add new nodes in force directed, but when I call the function QuickSearch(value) then it duplicates the graph.
Can anybody advise me how to add a new node only.
// Graph variables
var w = window.innerWidth;
var h = window.innerHeight;
var svg = d3.select("#svgData"),
scheme = ['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33','#a65628','#f781bf','#999999'],
width = +svg.attr(w),
height = +svg.attr(h),
color = d3.scaleOrdinal(d3.schemeCategory20);
//color = d3.scaleOrdinal(scheme);
var info = {
"nodes": [
{"id": "1", "name": "1", "group": 1},
{"id": "2", "name": "2", "group": 1},
{"id": "3", "name": "3", "group": 1},
{"id": "4", "name": "4", "group": 1},
{"id": "5", "name": "5", "group": 1}
],
"links": [
{"source": "1", "target": "2", "value": 1},
{"source": "1", "target": "3", "value": 1},
{"source": "1", "target": "4", "value": 1},
{"source": "1", "target": "5", "value": 1}
]
}
var marker = d3.select("#svgData").append('defs')
.append('marker')
.attr("id", "Triangle")
.attr('viewBox', '-0 -5 10 10')
.attr("refX", 25)
.attr("refY", 0)
.attr("markerUnits", 'userSpaceOnUse')
.attr("orient", 'auto')
.attr("markerWidth", 13)
.attr("markerHeight", 13)
.attr('xoverflow', 'visible')
.append('path')
.attr("d", 'M 0,-5 L 10 ,0 L 0,5');
function QuickSearch(value) {
var new_node = {};
//console.log(info.nodes);
new_node = {"id": value, "name": value, "group": 1};
info.nodes.findIndex(x => x.id == new_node.id) == -1 ? info.nodes.push(new_node) : console.log("object already exists")
createGraph(info);
};
function createGraph(graph) {
// Zoom
var zoom = d3.zoom()
.scaleExtent([0, 10])
.on("zoom", zoomed);
d3.select("#svgData").call(zoom);
function zoomed() {
const currentTransform = d3.event.transform;
container.attr("transform", currentTransform);
}
// Simulation
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(200))
.force("charge", d3.forceManyBody().strength(10).distanceMax(1000))
.force("center", d3.forceCenter(w / 2, h / 2))
.force('collision', d3.forceCollide().radius(30))
var container = svg.append("g");
var link = container.append("g")
.attr("class", "links")
.selectAll("path")
.data(graph.links)
.enter().append("path")
.attr("marker-end", "url(#Triangle)");
var value = d3.select("g.links")
.selectAll("text")
.data(graph.links)
.enter().append("text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.value;
});
var node = container.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr('stroke-width', 3)
.attr('stroke', function(d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", listInfo);
var lable = d3.select(".nodes")
.selectAll("text")
.data(graph.nodes)
.enter().append("text")
.text(function(d) { return d.name; });
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links)
function ticked() {
link
.attr("d", function(d) {
return "M" + d.source.x + "," + d.source.y
+ "C" + d.source.x + "," + (d.source.y + d.target.y) / 2
+ " " + d.target.x + "," + d.target.y
+ " " + d.target.x + "," + d.target.y;
})
.attr("stroke-dasharray", function() {
return this.getTotalLength() - 25;
});
value
.attr("x", function(d) { return (d.source.x + d.target.x)/2; })
.attr("y", function(d) { return (d.source.y + d.target.y)/2; });
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
lable
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y - 30; });
}
function slided(d) {
zoom.scaleTo(svg, d3.select(this).property("value"));
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.1).restart();
d.fx = d.x;
d.fy = d.y;
d3.select(this).classed("dragging", true);
d.fixed = true;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
//fix_nodes(d);
}
// Preventing other nodes from moving while dragging one node
function fix_nodes(this_node) {
node.each(function(d){
if (this_node != d){
d.fx = d.x;
d.fy = d.y;
}
});
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d3.select(this).classed("dragging", false);
//d.fixed = true;
}
var nodeText;
function listInfo(d) {
d3.select(this)
.select("text")
.text(function(d) { return d.name;})
var nodeText = d.name;
document.getElementById("nodeId").innerHTML = nodeText;
}
};
I tried several ways to use .exit().remove() option, but it doesn't work for me.
I have a force graph with colored nodes that I am trying to add labels to. Right now it has labels, but it's the small, hard to read ones native to the browser. How would I add labels that are easier to see?
<svg width="960" height="600"></svg>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory10);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
d3.json("got_relationships.json", function(error, graph) {
if (error) throw error;
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 5)
.attr("fill", function(d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("title")
.text(function(d) { return d.id; });
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
});
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
</script>
There is a confusion between "labels" and "tooltips". Traditionally, we name "labels" the texts that show up next to the nodes without user interaction, and we name "tooltips" the texts that show up when the user interacts with the nodes (for instance, hovering the nodes).
So, if you mean "labels", this is a solution: append the nodes as groups...
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.call(d3.drag().on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
And append the circles and the labels (as <text> elements) to them:
node.append("circle")
.attr("r", 5)
.attr("fill", function(d) { return color(d.group); });
node.append("text")
.attr("dx", 6)
.text(function(d) { return d.id; });
Here is a demo using (most of) your code, with a made up data:
var graph = {
nodes:[
{"id": "A", "group": 1},
{"id": "B", "group": 2},
{"id": "C", "group": 2},
{"id": "D", "group": 2},
{"id": "E", "group": 2},
{"id": "F", "group": 3},
{"id": "G", "group": 3},
{"id": "H", "group": 3},
{"id": "I", "group": 3}
],
links:[
{"source": "A", "target": "B", "value": 1},
{"source": "B", "target": "C", "value": 1},
{"source": "A", "target": "D", "value": 1},
{"source": "H", "target": "E", "value": 1},
{"source": "I", "target": "F", "value": 1},
{"source": "A", "target": "G", "value": 1},
{"source": "B", "target": "H", "value": 1},
{"source": "A", "target": "I", "value": 1},
]
};
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory20);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(40))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("circle")
.attr("r", 5)
.attr("fill", function(d) { return color(d.group); });
node.append("text")
.attr("dx", 6)
.text(function(d) { return d.id; });
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.node circle {
stroke: #fff;
stroke-width: 1.5px;
}
.node text{
fill: #666;
font-family: Helvetica
}
<svg width="400" height="300"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>