Labeled nodes, arrow on edges in D3.js - javascript

I am working on d3.js and facing two issue. I am unable to label nodes and show arrow on edges.
Here is my code :
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
</style>
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var graph = {
"nodes": [
{"number": "3006307180"},
{"number": "3215838129"},
{"number": "3216716348"},
{"number": "3217209263"},
{"number": "3212901630"},
{"number": "3035289939"}
],
"links": [
{"source": "3006307180", "target": "3215838129"},
{"source": "3216716348", "target": "3215838129"},
{"source": "3216716348", "target": "3217209263"},
{"source": "3212901630", "target": "3217209263"},
{"source": "3212901630", "target": "3035289939"}
]
};
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.number; }))
.force("charge", d3.forceManyBody()
.strength(-400))
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 15)
.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.number; });
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>
Here on this line node.append("title").text(function(d) { return d.number; }); It clearly show the nodes label on mouse over in tool tip BUT I want to show label beside OR on node. I have tried to replace title with text and nothing happens.
Second issue is I need to show arrow, for this I use:
svg.append("g").selectAll("marker")
.data(["end"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
and added this attribute in links .attr("marker-end", "url(#end)"); BUT nothing shows on edges.
These are the answers from almost all of the questions asked here related to my issue. I have gone through almost all of the questions BUT nothing solves my problem.
Please any idea, how to do it ? What i am doing wrong here ?

In SVG, circles are not container elements. That means that you cannot append a text element to a circle element.
The traditional solution here is turning node a group selection, to which you can append both circles and texts:
var node = svg.selectAll(null)
.data(graph.nodes)
.enter().append("g")
.attr("class", "nodes");
var circle = node.append("circle")
.attr("r", 15)
.attr("fill", function(d) {
return color(d.group);
}).call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var text = node.append("text")
.text(function(d) {
return d.number;
});
Then, inside the ticked function, translate the whole groups.
Here is your code with that change:
var graph = {
"nodes": [{
"number": "3006307180"
}, {
"number": "3215838129"
}, {
"number": "3216716348"
}, {
"number": "3217209263"
}, {
"number": "3212901630"
}, {
"number": "3035289939"
}],
"links": [{
"source": "3006307180",
"target": "3215838129"
}, {
"source": "3216716348",
"target": "3215838129"
}, {
"source": "3216716348",
"target": "3217209263"
}, {
"source": "3212901630",
"target": "3217209263"
}, {
"source": "3212901630",
"target": "3035289939"
}]
};
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.number;
}))
.force("charge", d3.forceManyBody()
.strength(-400))
.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(null)
.data(graph.nodes)
.enter().append("g")
.attr("class", "nodes");
var circle = node.append("circle")
.attr("r", 15)
.attr("fill", function(d) {
return color(d.group);
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var text = node.append("text")
.attr("dx", "1em")
.text(function(d) {
return d.number;
});
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;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
<svg width="600" height="400"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>

Working code for above question with labeled nodes and arrows on edges
Thought, I Should post my complete solution my self here.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.links line {
stroke: #999;
stroke-width: 3px;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
</style>
<svg width="1000" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var graph = {
"nodes": [
{"number": "3006307180"},
{"number": "3215838129"},
{"number": "3216716348"},
{"number": "3217209263"},
{"number": "3212901630"},
{"number": "3035289939"}
],
"links": [
{"source": "3006307180", "target": "3215838129", "value": "1440"},
{"source": "3216716348", "target": "3215838129", "value": "19870"},
{"source": "3216716348", "target": "3217209263", "value": "6177"},
{"source": "3212901630", "target": "3217209263", "value": "17236"},
{"source": "3212901630", "target": "3035289939", "value": "600"}
]
};
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory20c);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.number; }))
.force("charge", d3.forceManyBody()
.strength(-400))
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 15)
.attr("fill", function(d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var label = svg.selectAll(".mytext")
.data(graph.nodes)
.enter()
.append("text")
.text(function (d) { return d.number; })
.style("text-anchor", "middle")
.style("fill", "#555")
.style("font-family", "Arial")
.style("font-size", 10);
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
svg.append("svg:defs").selectAll("marker")
.data(["end"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 18)
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
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; })
.attr("marker-end", "url(#end)");
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
label
.attr("x", function(d){ return d.x; })
.attr("y", function (d) {return d.y - 10; });
}
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>

Related

How can I center the text in a node?

I'm learning about d3.js and the system of forces. I have a blocker because I am not able to add a text and it is perfectly centered within the circle. I had tried to create <text> but it does not work. How can I achieve it?
In this piece I tried to create a text element but it does not work.
var node = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter()
.append("circle")
.attr("r", 20)
.attr("fill", function(d){
return colorNode(d.group);
})
.style("stroke", function(d){
return colorNode(d.group);
})
UPDATE
I know that I must somehow make that within a g element, this content the circle and a text, but I can not make it work. obviously, I also do not know how to center the text inside the circle. My result is that the circle appears outside the force diagram. I have tried this, but not works:
var node = g.append("g")
.attr("class", "nodes")
.selectAll("g")
.data(graph.nodes)
.enter()
.append("g");
node.append("circle")
.attr("r", 20)
.attr("fill", function(d){
return colorNode(d.group);
})
.style("stroke", function(d){
return colorNode(d.group);
})
this is my full code:
https://plnkr.co/edit/JhjhFKQgKVtmYQXmgbzF?p=preview
All you need is setting the dominant-baseline to central, which centers the text vertically, and text-anchor to middle, which centers the text horizontally. Also, get rid of those x and y attributes.
This should be the selection:
var text = g.append("g")
.attr("class", "labels")
.selectAll("text")
.data(graph.nodes)
.enter()
.append("text")
.style("dominant-baseline", "central")
.style("text-anchor", "middle")
.style("font-family", "sans-serif")
.style("font-size", "0.7em")
.text(function(d) {
return d.lotid;
});
Here is your code with those changes:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .9;
}
</style>
<body>
<div id="grafica_back" style="width:100wh"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var w = window,
d = document,
e = d.documentElement,
g = d.getElementsByTagName('body')[0],
width = w.innerWidth || e.clientWidth || g.clientWidth,
height = w.innerHeight || e.clientHeight || g.clientHeight;
var colorNode = d3.scaleOrdinal()
.range(d3.schemeCategory20),
colorLink = d3.scaleOrdinal()
.range(d3.schemeCategory10)
var svg = d3.select("#grafica_back").append("svg")
.attr("width", width)
.attr("height", height);
var graph = {
"nodes": [{
"id": "18362286",
"lotid": "TEST",
"epoch": 1511295513000,
"group": 1,
"sourceOnly": true
},
{
"id": "18362287",
"lotid": "TEST",
"epoch": 1511324313000,
"group": 2,
"sourceOnly": false
}
],
"links": [{
"source": "18362286",
"target": "18362287",
"reltype": "GENEALOGY"
}]
};
var width = 400,
height = 200;
var simulation = d3.forceSimulation()
.nodes(graph.nodes);
simulation
.force("charge_force", d3.forceManyBody().strength(-100))
.force("center_force", d3.forceCenter(width / 2, height / 2))
.force("links", d3.forceLink(graph.links).id(function(d) {
return d.id;
}).distance(100).strength(0.1))
.force("collide", d3.forceCollide().radius(2))
;
simulation
.on("tick", ticked);
//add encompassing group for the zoom
var g = svg.append("g")
.attr("class", "everything");
//Create deffinition for the arrow markers showing relationship directions
g.append("defs").append("marker")
.attr("id", "arrow")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 23)
.attr("refY", 0)
.attr("markerWidth", 8)
.attr("markerHeight", 8)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
var link = g.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke", function(d) {
console.log(d);
return colorLink(d.group);
})
.attr("marker-end", "url(#arrow)");
var node = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter()
.append("circle")
.attr("r", 20)
.attr("fill", function(d) {
return colorNode(d.group);
})
.style("stroke", function(d) {
return colorNode(d.group);
})
//add drag capabili
var drag_handler = d3.drag()
.on("start", drag_start)
.on("drag", drag_drag)
.on("end", drag_end);
drag_handler(node);
var text = g.append("g")
.attr("class", "labels")
.selectAll("text")
.data(graph.nodes)
.enter()
.append("text")
.style("dominant-baseline", "central")
.style("text-anchor", "middle")
.style("font-family", "sans-serif")
.style("font-size", "0.7em")
.text(function(d) {
return d.lotid;
});
node.on("click", function(d) {
d3.event.stopImmediatePropagation();
self.onNodeClicked.emit(d.id);
});
node.append("title")
.text(function(d) {
d.lotid;
});
//add zoom capabilities
var zoom_handler = d3.zoom()
.on("zoom", zoom_actions);
zoom_handler(svg);
//Drag functions
//d is the node
function drag_start(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
//make sure you can't drag the circle outside the box
function drag_drag(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function drag_end(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
//Zoom functions
function zoom_actions() {
g.attr("transform", d3.event.transform)
}
function ticked() {
//update circle positions each tick of the simulation
node
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
//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;
});
text
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
</script>

Adding Multiple shaped nodes to a force directed network diagram in d3v4

I am Trying to add rectangle and circle nodes in d3v4, the graph works although the nodes are all grouped together in one corner and their positions are not being updated. I can't work out what i'm doing wrong?
I have tried looking for examples online but cant seem to find any that are using d3v4 specifically
<!DOCTYPE html>
<meta charset="UTF-8">
<style>
</style>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// Properties
var width = 800;
var height = 600;
var nominal_stroke = 4;
// Simulation
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody().strength(-400))
.force("center", d3.forceCenter(width / 2, height / 2));
// Create SVG window
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g");
svg.style("cursor", "move");
// Load JSON data
d3.json("./network.json", function(error, graph) {
console.log(graph);
if (error) throw error;
// Draw links
var link = g.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", nominal_stroke)
.style("stroke", "#999")
.style("stroke-opacity", 0.6);
// Draw nodes
var node = g.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// Setup node properties
var circle = node.append("path")
.attr("d", d3.symbol()
.type(function (d) {
if
(d.shape == "rect") {
return d3.symbolSquare;
} else if
(d.shape == "circle") {
return d3.symbolCircle;
}
})
.size(400))
.style("stroke", "#999")
.style("stroke-opacity", 0.6)
.style("fill", function (d) {
return "blue"
});
// Add titles
node.append("title")
.text(function (d) {return d.id;});
// Start Simulation
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
// Refresh page
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; });
}
// Zoom handler
svg.call(d3.zoom()
.scaleExtent([1 / 2, 8])
.on("zoom", zoomed));
function zoomed() {
node.attr("transform", d3.event.transform);
link.attr("transform", d3.event.transform);
}
});
// Functions
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 = d.x;
d.fy = d.y;
}
function openLink() {
return function (d) {
var url = "";
if (d.url != "") {
url = d.url
}
window.open(url)
}
}
</script>
</body>
You should not use cx and cy in the ticked function, since you're dealing with <path>s, not <circle>s. You should use translate instead.
Therefore, it has to be:
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
Here is your code with that change (I'm using fake data here):
var width = 600;
var height = 400;
var nominal_stroke = 4;
// Simulation
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}))
.force("charge", d3.forceManyBody().strength(-400))
.force("center", d3.forceCenter(width / 2, height / 2));
// Create SVG window
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g");
svg.style("cursor", "move");
graph = {
nodes: [{
id: 1,
shape: "rect"
}, {
id: 2,
shape: "circle"
}, {
id: 3,
shape: "rect"
}, {
id: 4,
shape: "rect"
}, {
id: 5,
shape: "circle"
}, {
id: 6,
shape: "circle"
}, {
id: 7,
shape: "circle"
}],
links: [{
source: 1,
target: 2
}, {
source: 1,
target: 3
}, {
source: 1,
target: 4
}, {
source: 1,
target: 5
}, {
source: 3,
target: 6
}, {
source: 3,
target: 7
}]
}
// Draw links
var link = g.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", nominal_stroke)
.style("stroke", "#999")
.style("stroke-opacity", 0.6);
// Draw nodes
var node = g.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// Setup node properties
var circle = node.append("path")
.attr("d", d3.symbol()
.type(function(d) {
if (d.shape == "rect") {
return d3.symbolSquare;
} else if (d.shape == "circle") {
return d3.symbolCircle;
}
})
.size(400))
.style("stroke", "#999")
.style("stroke-opacity", 0.6)
.style("fill", function(d) {
return "blue"
});
// Add titles
node.append("title")
.text(function(d) {
return d.id;
});
// Start Simulation
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
// Refresh page
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 + ")";
});
}
// Zoom handler
svg.call(d3.zoom()
.scaleExtent([1 / 2, 8])
.on("zoom", zoomed));
function zoomed() {
g.attr("transform", d3.event.transform);
}
// Functions
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 = d.x;
d.fy = d.y;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
PS: Your zoom function is not working, which is a different problem. I also fixed it.

d3js Force Directed Graph - Click on node to popup infobox which read from JSON

I have been working with Force-Directed Graph but I am still new to D3js and Javascript.
I want to able to click on the node and the infobox popup on the page and print some information about that node (from JSON).
Example of my json file:
{"directed": false, "graph": {},
"nodes": [{"id": 0, "name":"Ant" , "info":"Small Animal"},
{"id": 1 , "name":"Apple" , "info":"Fruit"},
{"id": 2 , "name":"Bat" , "info":"Fly Animal"},
{"id": 3 , "name":"Boat" , "info":"Vehicle"},
{"id": 4 , "name":"Banana" , "info":"Long cute Fruit"},
{"id": 5 , "name":"Cat" , "info":"Best Animal"}],
"links": [{"source": 0, "target": 0 , "weight":1}, {"source": 0, "target": 2, "weight": 0.3},
{"source": 0, "target": 5, "weight":0.8}, {"source": 1, "target": 1, "weight":1},
{"source": 1, "target": 4, "weight":0.5}, {"source": 2, "target": 2, "weight":1},
{"source": 3, "target": 3, "weight":1}, {"source": 4, "target": 4, "weight":1},
{"source": 5, "target": 5, "weight":1}],
"multigraph": false}
So when I click on a node. It should popup something like:
Name: Ant
Info: Small Animal
Connected to: Bat with 0.3 weight , Cat with 0.8 weight
My graph code were pretty much like the example of force-directed that I link above.
Here's a quick implementation, it builds your "infobox" using SVG:
var tip;
node.on("click", function(d){
if (tip) tip.remove();
tip = svg.append("g")
.attr("transform", "translate(" + d.x + "," + d.y + ")");
var rect = tip.append("rect")
.style("fill", "white")
.style("stroke", "steelblue");
tip.append("text")
.text("Name: " + d.name)
.attr("dy", "1em")
.attr("x", 5);
tip.append("text")
.text("Info: " + d.info)
.attr("dy", "2em")
.attr("x", 5);
var con = graph.links
.filter(function(d1){
return d1.source.id === d.id;
})
.map(function(d1){
return d1.target.name + " with weight " + d1.weight;
})
tip.append("text")
.text("Connected to: " + con.join(","))
.attr("dy", "3em")
.attr("x", 5);
var bbox = tip.node().getBBox();
rect.attr("width", bbox.width + 5)
.attr("height", bbox.height + 5)
});
Running code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
text {
font-family: sans-serif;
}
</style>
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
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; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
d3.json("https://jsonblob.com/api/15daa79f-7573-11e8-b9d7-1b0997147957", function(error, graph) {
if (error) throw error;
link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) { return d.weight * 3; });
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));
var tip;
svg.on("click", function(){
if (tip) tip.remove();
});
node.on("click", function(d){
d3.event.stopPropagation();
if (tip) tip.remove();
tip = svg.append("g")
.attr("transform", "translate(" + d.x + "," + d.y + ")");
var rect = tip.append("rect")
.style("fill", "white")
.style("stroke", "steelblue");
tip.append("text")
.text("Name: " + d.name)
.attr("dy", "1em")
.attr("x", 5);
tip.append("text")
.text("Info: " + d.info)
.attr("dy", "2em")
.attr("x", 5);
var con = graph.links
.filter(function(d1){
return d1.source.id === d.id;
})
.map(function(d1){
return d1.target.name + " with weight " + d1.weight;
})
tip.append("text")
.text("Connected to: " + con.join(","))
.attr("dy", "3em")
.attr("x", 5);
var bbox = tip.node().getBBox();
rect.attr("width", bbox.width + 5)
.attr("height", bbox.height + 5)
});
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>
You can add click event on node using .on('click', function(d){ });
For example:
node.on("click", function(d){
console.log(d);
// here you can access data of node using d.key
alert("You clicked on node " + d.name);
});

Perpetual Motion in the Force Layout

In the example of Mike Bostock - https://bl.ocks.org/mbostock/4062045
Is it possible to have the nodes in simple|random|slight perpetual motion to make it visualising appealing? If yes, How do I begin with the same?
It can be as simple as:
simulation
.nodes(graph.nodes)
.on("tick", ticked)
// on the end of the simulation, restart it
.on("end", function(){
simulation.alphaTarget(0.5).restart();
});
After it does the initial "settle", this will give the appearance of it "floating around":
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
</style>
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
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; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
d3.json("https://jsonblob.com/api/901c4b8a-1162-11e7-a0ba-a1c27e793e26", 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)
.on("end", function(){
simulation.alphaTarget(0.5).restart();
})
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>

Making D3 like Force-Directed Graph with PNGs

I've been trying to make D3 like Force-Directed Graph (example: https://bl.ocks.org/mbostock/4062045) with PNGs (meaning the dots should be pictures).
Here is a visual idea:
A different approach I tried is to map each graphic element into parallax.js (http://matthew.wagerfield.com/parallax/) and draw a line between the center of each graphic element, but I do not know how to do that, just yet.
Since you didn't post your code to create the force, I'll provide a general answer. You may have to adapt it according to your specific code.
The basic idea here is appending group elements for each node, and appending both the circles and the images to those groups. Here I'm using 40x40 PNGs:
var node = svg.selectAll("foo")
.data(nodes)
.enter()
.append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var nodeCircle = node.append("circle")
.attr("r", 20)
.attr("stroke", "gray")
.attr("fill", "none");
var nodeImage = node.append("image")
.attr("xlink:href", d => d.image)
.attr("height", "40")
.attr("width", "40")
.attr("x", -20)
.attr("y", -20);
The url of each image is in the data array of the nodes:
var nodes = [{
"id": "foo",
"image": "https://icons.iconarchive.com/icons/google/chrome/48/Google-Chrome-icon.png"
}, {
"id": "bar",
"image": "https://icons.iconarchive.com/icons/carlosjj/mozilla/48/Firefox-icon.png"
}, {
"id": "baz",
"image": "https://icons.iconarchive.com/icons/johanchalibert/mac-osx-yosemite/48/safari-icon.png"
}, {
"id": "barbaz",
"image": "https://icons.iconarchive.com/icons/ampeross/smooth/48/Opera-icon.png"
}];
Here is a demo:
var width = 300;
var height = 200;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var nodes = [{
"id": "Chrome",
"image": "https://icons.iconarchive.com/icons/google/chrome/48/Google-Chrome-icon.png"
}, {
"id": "Firefox",
"image": "https://icons.iconarchive.com/icons/carlosjj/mozilla/48/Firefox-icon.png"
}, {
"id": "Safari",
"image": "https://icons.iconarchive.com/icons/johanchalibert/mac-osx-yosemite/48/safari-icon.png"
}, {
"id": "Opera",
"image": "https://icons.iconarchive.com/icons/ampeross/smooth/48/Opera-icon.png"
}];
var edges = [{
"source": 0,
"target": 1
}, {
"source": 0,
"target": 2
}, {
"source": 0,
"target": 3
}];
var simulation = d3.forceSimulation()
.force("link", d3.forceLink())
.force("charge", d3.forceManyBody().strength(-1000))
.force("center", d3.forceCenter(width / 2, height / 2));
var links = svg.selectAll("foo")
.data(edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
var color = d3.scaleOrdinal(d3.schemeCategory20);
var node = svg.selectAll("foo")
.data(nodes)
.enter()
.append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var nodeCircle = node.append("circle")
.attr("r", 20)
.attr("stroke", "gray")
.attr("stroke-width", "2px")
.attr("fill", "white");
var nodeImage = node.append("image")
.attr("xlink:href", d => d.image)
.attr("height", "40")
.attr("width", "40")
.attr("x", -20)
.attr("y", -20)
var texts = node.append("text")
.style("fill", "black")
.attr("dx", 20)
.attr("dy", 8)
.text(function(d) {
return d.id;
});
simulation.nodes(nodes);
simulation.force("link")
.links(edges);
simulation.on("tick", function() {
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;
})
node.attr("transform", (d) => "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;
}
<script src="https://d3js.org/d3.v4.min.js"></script>

Categories

Resources