I am using a force based layout using D3 and was wondering if it's possible that when I click either the nodes or the labels, I am automatically taken to the url stored in that node/label? If it is, how can I achieve this?
This is how the graph will actually look (labels indicate the URL itself)
I am sharing the javascript code I am using to generate the D3 graph. In the code, I am currently using dummy data which will actually be replaced by URLs.
document.addEventListener('DOMContentLoaded', function () {
drawVisual();
});
function drawVisual()
{
//document.getElementById('myMSG').innerHTML = "last name"+localStorage.lastname;
/*var getArr = [];
getArr = JSON.parse(localStorage.getItem('storeArray'));
document.getElementById('myMSG').innerHTML = getArr[1].parentURL;*/
//var w = 1024, h = 768;
var w=window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;
var h=window.innerHeight
|| document.documentElement.clientHeight
|| document.body.clientHeight;
//var w = 1024, h = 768;
//var vis = d3.select("#tab_5_contents").append("svg:svg").attr("width", w).attr("height", h);
var vis = d3.select("#forcedLayoutGraph").append("svg:svg").attr("width", w).attr("height", h);
var QueuedORG = [];
//QueuedORG = JSON.parse(localStorage.getItem('storeArray'));
QueuedORG.push({url: "Root", parentURL: "Root", used:0});
QueuedORG.push({url: "a", parentURL: "Root", used:0});
QueuedORG.push({url: "b", parentURL: "Root", used:0});
QueuedORG.push({url: "c", parentURL: "Root", used:0});
QueuedORG.push({url: "d", parentURL: "Root", used:0});
QueuedORG.push({url: "e", parentURL: "a", used:0});
QueuedORG.push({url: "f", parentURL: "a", used:0});
QueuedORG.push({url: "g", parentURL: "a", used:0});
QueuedORG.push({url: "h", parentURL: "a", used:0});
QueuedORG.push({url: "p", parentURL: "b", used:0});
QueuedORG.push({url: "q", parentURL: "b", used:0});
QueuedORG.push({url: "r", parentURL: "b", used:0});
QueuedORG.push({url: "x", parentURL: "c", used:0});
QueuedORG.push({url: "y", parentURL: "x", used:0});
QueuedORG.push({url: "y", parentURL: "c", used:0});
QueuedORG.push({url: "x", parentURL: "a"});
QueuedORG.push({url: "y", parentURL: "b"});
var nodes = [];
var labelAnchors = [];
var labelAnchorLinks = [];
var links = [];
for(var i = 0; i < QueuedORG.length; i++)
{
var nodeExists = 0;
//check to see if a node for the current url has already been created. If yes, do not create a new node
for(var j = 0; j < nodes.length; j++)
{
if(QueuedORG[i].url == nodes[j].label)
nodeExists = 1;
}
if (nodeExists == 0)
{
var urlLabel = QueuedORG[i].url;
//remove 'http://' part
/*urlLabel = urlLabel.split("http://")[1];
if(urlLabel.match("www"))
urlLabel = urlLabel.split("www.")[1];
var rest = urlLabel.split("\.")[1];
urlLabel = urlLabel.split("\.")[0];*/
var node = {
label : QueuedORG[i].url,
category : QueuedORG[i].category
};
nodes.push(node);
labelAnchors.push({
node : node
});
labelAnchors.push({
node : node
});
}
};
for(var i=0;i<nodes.length; i++)
{
console.log("node i:"+i+nodes[i]+"\n");
console.log("labelAnchor i:"+i+labelAnchors[i]+"\n");
}
//To create links for connecting nodes
for(var i = 0; i < QueuedORG.length; i++)
{
var srcIndx = 0, tgtIndx = 0;
for(var j = 0; j < nodes.length; j++)
{
if( QueuedORG[i].url == nodes[j].label ) //to find the node number for the current url
{
srcIndx = j;
}
if( QueuedORG[i].parentURL == nodes[j].label ) //to find the node number for the parent url
{
tgtIndx = j;
}
}
//console.log("src:"+srcIndx+" tgt:"+tgtIndx);
//connecting the current url's node to the parent url's node
links.push({
source : srcIndx,
target : tgtIndx,
weight : 1,
});
labelAnchorLinks.push({
source : srcIndx * 2,
target : srcIndx * 2 + 1,
weight : 1
});
};
var force = d3.layout.force().size([w, h]).nodes(nodes).links(links).gravity(1).charge(-10000).linkStrength(function(x) {
return x.weight * 10 // charge is for inter-node repel, link distance is node-node distance
});
force.linkDistance(function(d) {
return d.weight * 100;
});
force.start();
var force2 = d3.layout.force().nodes(labelAnchors).links(labelAnchorLinks).gravity(0).linkStrength(10).charge(-500).size([w, h]); //charge is for inter-label repel, link distance is node-label distance
force2.linkDistance(function(d) {
return d.weight * 10;
});
force2.start();
var link = vis.selectAll("line.link").data(links).enter().append("svg:line").attr("class", "link").style("stroke", "#CCC");
var colors = {"1": "black", "2": "blue", "3": "red"}; // 1=root node 2=blog nodes 3=.org nodes
var shape = {"1": "diamond", "2": "cross", "3": "circle"};
var node = vis.selectAll("g.node").data(force.nodes()).enter().append("path").attr("class", "node").call(force.drag);
//node.append("circle").attr("r", 5).style("stroke", "#FFF").style("stroke-width", 3).attr("class", function(d) {return "node category"+d.category});
node.attr("d", d3.svg.symbol().type(function(d) {return shape[d.category];})).style("stroke", "#FFF").style("fill", function(d){ return colors[d.category];});
var anchorLink = vis.selectAll("line.anchorLink").data(labelAnchorLinks)//.enter().append("svg:line").attr("class", "anchorLink").style("stroke", "#999");
var anchorNode = vis.selectAll("g.anchorNode").data(force2.nodes()).enter().append("svg:g").attr("class", "anchorNode");
anchorNode.append("svg:circle").attr("r", 0).style("fill", "#FFF");
anchorNode.append("svg:text").text(function(d, i) {
return i % 2 == 0 ? "" : d.node.label
}).style("fill", "#555").style("font-family", "Arial").style("font-size", 12);
var updateLink = function() {
this.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 updateNode = function() {
this.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
force.on("tick", function() {
force2.start();
node.call(updateNode);
anchorNode.each(function(d, i) {
if(i % 2 == 0) {
d.x = d.node.x;
d.y = d.node.y;
} else {
var b = this.childNodes[1].getBBox();
var diffX = d.x - d.node.x;
var diffY = d.y - d.node.y;
var dist = Math.sqrt(diffX * diffX + diffY * diffY);
var shiftX = b.width * (diffX - dist) / (dist * 2);
shiftX = Math.max(-b.width, Math.min(0, shiftX));
var shiftY = 5;
this.childNodes[1].setAttribute("transform", "translate(" + shiftX + "," + shiftY + ")");
}
});
anchorNode.call(updateNode);
link.call(updateLink);
anchorLink.call(updateLink);
});
}
Generally speaking, you can add click events to SVG elements in D3.js using
.on('click', function(d, i) {
window.location.href = d.url;
})
d
is the data object and the i
is in the index of d within the collection.
Just add that click handler to your text node as well as your node (path) node like in this fiddle http://jsfiddle.net/jNyrf/
You have two options here.
.on("click", ...)
handler to set the current page to the target.a
element with .attr("xlink:href", url)
that contains the element acting as a hyperlink to set the link in a more traditional way.More information in this question/answer, although I believe you don't have to import the xlink
namespace explicitly, at least not in the latest version of D3.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With