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>
Related
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 am trying to get the below code working with adding labels, still no luck, seems that my labels are written on top of the page instead of on the node itself. I think it is a binding issue with SVG.
I tried to add the title but I didn't manage to display it.
<!-- INITAL SETTINGS ------------------------------------------------------------------------------->
<!-------------------------------------------------------------------------------------------------->
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.links line {
stroke: #999;
stroke-opacity: 0.5;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.2px;
}
</style>
<!-- the SVG viewport will be 1100px by 900px -->
<svg width="1100" height="900" viewbox="0 0 1100 900">
<text> </text>
<!-- SVG content drawn onto the SVG canvas -->
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// DATA DEFINITIONS NODES & LINKS ------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
var graph = {
"nodes": [
{ "id": "xxx1", "group": 3 },
{ "id": "xxx2", "group": 1 },
{ "id": "xxx3", "group": 1 }
],
"links": [
{ "source": "xxx1", "target": "xxx2", "value": 42 },
{ "source": "xxx2", "target": "xxx3", "value": 6 },
{ "source": "xxx1", "target": "xxx3", "value": 13 }
]
};
// ---------------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------
// D3 CODE
// ---------------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------
// create the construct/canvas for drawing
var svg = d3.select("svg")
//set width/height
width = +svg.attr("width"),
height = +svg.attr("height")
preserveAspectRatio = "xMinYMin meet"
//color definition
var color = d3.scaleOrdinal(d3.schemeCategory20);
//Creates a new simulation with the specified array of nodes
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width/2.3, height/2.3))
// adding links
//The g element is a container element for grouping related graphics together
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); });
//adding nodes
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 6)
.attr("fill", function (d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
//var text = svg.append("g")
// .attr("class", "text")
// .selectAll("circle")
// .data(graph.nodes)
// .enter().append("id")
// .attr("cx", "12")
// .attr("cy", ".35em")
// .attr("text-anchor", "middle")
// .text(function(d) { return d.id });
// adding title
node.append("title")
.text(function (d) { return (d.id); });
var text = svg.append("g")
.attr("class", "labels")
.selectAll("text")
.data(graph.nodes)
.enter().append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.id });
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
// ticked functionality
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; })
.call(force.drag);
}
//The data and the circle element’s position are updated during the drag event
// when dragged
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
// when dragged completed
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
// when dragged ended
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
You have to move the texts as well in the tick function:
text.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
Here is your code with that change:
var graph = {
"nodes": [{
"id": "xxx1",
"group": 3
}, {
"id": "xxx2",
"group": 1
}, {
"id": "xxx3",
"group": 1
}
],
"links": [{
"source": "xxx1",
"target": "xxx2",
"value": 42
}, {
"source": "xxx2",
"target": "xxx3",
"value": 6
}, {
"source": "xxx1",
"target": "xxx3",
"value": 13
}]
};
// create the construct/canvas for drawing
var svg = d3.select("svg")
//set width/height
width = +svg.attr("width"),
height = +svg.attr("height")
preserveAspectRatio = "xMinYMin meet"
//color definition
var color = d3.scaleOrdinal(d3.schemeCategory20);
//Creates a new simulation with the specified array of nodes
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2.3, height / 2.3))
// adding links
//The g element is a container element for grouping related graphics together
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);
});
//adding nodes
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 6)
.attr("fill", function(d) {
return color(d.group);
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// adding title
node.append("title")
.text(function(d) {
return (d.id);
});
var text = svg.append("g")
.attr("class", "labels")
.selectAll("text")
.data(graph.nodes)
.enter().append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.id
});
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
// ticked functionality
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;
});
text.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
}
//The data and the circle element’s position are updated during the drag event
// when dragged
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
// when dragged completed
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
// when dragged ended
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
.links line {
stroke: #999;
stroke-opacity: 0.5;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.2px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="500" height="400" viewbox="0 0 500 400">
<text> </text>
</svg>
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>