I currently have a D3 that displays a force directed network and will collapse its children on click. I would like to to also display the names of the nodes on hover. Something like this: https://bl.ocks.org/mbostock/1212215
Below is my code so far.
.node {
cursor: pointer;
stroke: #3182bd;
stroke-width: 1.5px;
}
.link {
fill: none;
stroke: #9ecae1;
stroke-width: 1.5px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960,
height = 500,
root;
var force = d3.layout.force()
.size([width, height])
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
//Added markers to indicate that this is a directed graph
svg.append("defs").selectAll("marker")
.data(["arrow"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 4)
.attr("markerHeight", 4)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var link = svg.selectAll(".link"),
node = svg.selectAll(".node");
d3.json("test.json", function(json) {
root = json;
//Give nodes ids and initialize variables
for(var i=0; i<root.nodes.length; i++) {
var node = root.nodes[i];
node.id = i;
node.collapsing = 0;
node.collapsed = false;
}
//Give links ids and initialize variables
for(var i=0; i<root.links.length; i++) {
var link = root.links[i];
link.source = root.nodes[link.source];
link.target = root.nodes[link.target];
link.id = i;
}
// for (var i=0; i<root.nodes.length; i++){
// var node = root.nodes[i];
// }
update();
});
function update() {
//Keep only the visible nodes
var nodes = root.nodes.filter(function(d) {
return d.collapsing == 0;
});
var links = root.links;
//Keep only the visible links
links = root.links.filter(function(d) {
return d.source.collapsing == 0 && d.target.collapsing == 0;
});
force
.nodes(nodes)
.links(links)
.start();
// Update the links…
link = link.data(links, function(d) { return d.id; });
// Exit any old links.
link.exit().remove();
// Enter any new links.
link.enter().insert("line", ".node")
.attr("class", "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 the nodes…
node = node.data(nodes, function(d){ return d.id; }).style("fill", color);
// Exit any old nodes.
node.exit().remove();
// Enter any new nodes.
node.enter().append("circle")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return Math.sqrt(d.size) / 10 || 4.5; })
.style("fill", color)
.on("click", click)
.call(force.drag);
}
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("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
// Color leaf nodes orange, and packages white or blue.
function color(d) {
return d.collapsed ? "#3182bd" : d.children ? "#c6dbef" : "#fd8d3c";
}
// Toggle children on click.
function click(d) {
if (!d3.event.defaultPrevented) {
//check if link is from this node, and if so, collapse
root.links.forEach(function(l) {
if(l.source.id == d.id) {
if(d.collapsed){
l.target.collapsing--;
} else {
l.target.collapsing++;
}
}
});
d.collapsed = !d.collapsed;
}
update();
}
</script>
My issue is with the update function. I was able to get it to display, but when I clicked to collapse, it wouldn't show the labels after. Im not sure if I should be tweeting the tick or what.
thanks!
Group the circle and text labels using SVG group element as shown below.
var groupNodes = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate("+d.x+","+d.y+")"; });
.on("click", click)
.on("mouseover", function(){
d3.select(this).select("text").style("display","block");
})
.on("mouseout", function(){
d3.select(this).select("text").style("display","none");
})
.call(force.drag);
groupNodes.append("circle")
.attr("r", function(d) { return Math.sqrt(d.size) / 10 || 4.5; })
.style("fill", color);
var label = groupNodes.append("text")
.attr("dy", ".35em")
.style("display", "none)
.text(function(d) { return d.name; //Use the key which holds the name value });
The above code replaces following part of the code in the update function.
node.enter().append("circle")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return Math.sqrt(d.size) / 10 || 4.5; })
.style("fill", color)
.on("click", click)
.call(force.drag);
and now change the tick function as follows
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+")";
});
}
Now all other functionalities should work as expected.
Related
I'm trying to visualize a graph using D3.js via web sockets. I'm trying to display node label ( can be seen in the code below ), but it does not seem to appear at all. Please see the function start(). What is wrong here?
<script>
var width = 1900,
height = 1080;
var color = d3.scale.category10();
var nodes = [],
links = [];
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.charge(-100)
.gravity(0.1)
.linkDistance(100)
.size([width, height])
.on("tick", tick);
var svg = d3.select("body").append("svg").attr("width",width).attr("height", height);
var node = svg.selectAll(".node"),
link = svg.selectAll(".link");
function tick() {
node.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; });
}
// Add and remove elements on the graph object
function addNode(id) {
nodes.push({"name":id, "id":id});
start();
}
function addEdge(edgeId,sourceId,targetId) {
var sourceNode = findNode(sourceId);
var targetNode = findNode(targetId);
if((sourceNode !== undefined) && (targetNode !== undefined)) {
links.push({"edgeId":edgeId, "source": sourceNode, "target": targetNode});
start();
}
}
function removeEdge(edgeId) {
for (var i = 0; i < links.length; i++) {
if (links[i].edgeId == edgeId) {
links.splice(i, 1);
break;
}
}
start();
}
var findNode = function (id) {
for (var i=0; i < nodes.length; i++) {
if (nodes[i].id === id)
return nodes[i]
};
}
function start() {
var drag = force.drag().origin(function(d) { return d; }).on("dragstart", dragstarted).on("drag", dragged).on("dragend", dragended);
link = link.data(force.links(), function(d) { return d.source.id + "-" + d.target.id; });
link.enter().insert("line", ".node").attr("class", "link");
link.exit().remove();
node = node.data(force.nodes(), function(d) { return d.id;});
node.enter().append("circle").attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(drag);
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
node.exit().remove();
force.start();
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
var socket = new WebSocket('ws://localhost:8887');
socket.onopen = function(){
console.log("Connection established, handle with function");
};
socket.onmessage = function(evt){
var obj = JSON.parse(evt.data);
if(obj.operation == "nodeAdded")
{
addNode( obj.nodeId );
}
if(obj.operation == "edgeAdded")
{
addEdge(obj.edgeId,obj.fromNodeId,obj.toNodeId);
}
if(obj.operation == "edgeRemoved")
{
removeEdge(obj.edgeId);
}
}
</script>
You will have to group the circles and corresponding labels for each node. Try this way.
node = node.data(force.nodes(), function(d) { return d.id;})
.enter().append("g")
.attr("class", "node")
.call(drag);
node.append("circle")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); });
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
Also update the tick function as shown below.
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 + ")"; });
}
By default text elements are of the white color. Add .style("fill", "black"), so you can see them.
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.style("fill", "black")
.text(function(d) { return d.name });
Also your node is the <circle> element. You can't append <text> element to the <circle> element ( it's not a container ). Use a <g> element and append <circle> and <text> to it or append <text> element to the <svg> element.
I'm able to get images and labels to show for the nodes, but they show at the top left of the screen.
Nodes show up in the correct position when I use this
.enter().append("circle")
Labels and node images show at the top left (incorrect) when I use this:
.enter().append("g")
This works with append "circle" (commented out in the code below):
When I comment out append circle and use append "g" (in order to use node images and labels) the images and labels all show up near (0,0) instead of near the node:
Also, what exactly is append "g"? Where is the documentation to find out what's possible with append "g"?
Here is all the code:
<script>
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size(\[width, height\]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var graph = getData();
var nodeMap = {};
graph.nodes.forEach(function(d) { nodeMap\[d.name\] = d; });
graph.links.forEach(function(l) {
l.source = nodeMap\[l.source\];
l.target = nodeMap\[l.target\];
})
force.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke", function(d) {
return d.line_color;
})
.style("stroke-width", function(d) {
return Math.sqrt(d.value)+1;
});
var node = svg.selectAll(".node")
.data(graph.nodes)
// .enter().append("circle")
// .attr("class", "node")
// .attr("r", 10)
// .style("fill", function(d) { return d.fill_color; })
// .call(force.drag);
.enter().append("g")
.attr("class", "node")
.attr("r", 15)
.style("fill", function(d) { return d.fill_color; })
.on("click", function(d){
alert("You clicked on node " + d.name);
})
.call(force.drag);
node.append("title")
.text(function(d) { return d.label; });
node.append("image")
.attr("xlink:href", function(d) { return d.image_url })
.attr("x", -8)
.attr("y", -8)
.attr("width", 26)
.attr("height", 26);
node.append("text")
.attr("dx", function(d) {
if (d.image_url == "/profile.png"){
return 100;
}
else{
return 16;
}
})
.attr("dy", function(d) {
if (d.image_url == "/profile.png"){
return 100;
}
else{
return ".35em";
}
})
// .attr("dy", ".35em")
.text(function(d) { return d.name });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
function getData() {
return {
"nodes":\[
{"name":"user1","image_url":"http://upload.wikimedia.org/wikipedia/en/thumb/8/80/Wikipedia-logo-v2.svg/103px-Wikipedia-logo-v2.svg.png","fill_color":"blue","text_color":"black"},
{"name":"user2","image_url":"http://upload.wikimedia.org/wikipedia/en/thumb/8/80/Wikipedia-logo-v2.svg/103px-Wikipedia-logo-v2.svg.png","fill_color":"blue","text_color":"black"},
{"name":"user3","image_url":"http://upload.wikimedia.org/wikipedia/en/thumb/8/80/Wikipedia-logo-v2.svg/103px-Wikipedia-logo-v2.svg.png","fill_color":"blue","text_color":"black"},
{"name":"tag1","image_url":"","fill_color":"blue","text_color":"black"},
{"name":"tag2","image_url":"","fill_color":"blue","text_color":"black"},
{"name":"tag3","image_url":"","fill_color":"blue","text_color":"black"}
\],
"links":\[
{"source":"tag1","target":"user1","value":1,"line_color":"green"},
{"source":"tag2","target":"user1","value":1,"line_color":"green"},
{"source":"tag3","target":"user1","value":1,"line_color":"green"},
{"source":"tag1","target":"user2","value":1,"line_color":"green"},
{"source":"tag2","target":"user2","value":1,"line_color":"green"}
\]
};
}
</script>
With .append("g") you insert a SVG Group Element.
The problem is, that you try to apply attributes that are for circles, like the radius with .attr("r",15), to the group element.
You have to use circles if you want to draw a circle. Group elements do not have any shape. They are used to group elements like circles.
A solution would be to append the g element and transform it to the location of the node. I updated your code in the following snippet. I used the group elements and added the circle, image and text inside the group elements.
Moreover I removed the backslashes before each angular bracket and set the title to the field name instead of label.
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var graph = getData();
var nodeMap = {};
graph.nodes.forEach(function(d) { nodeMap[d.name] = d; });
graph.links.forEach(function(l) {
l.source = nodeMap[l.source];
l.target = nodeMap[l.target];
})
force.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter()
.append("line")
.attr("class", "link")
.style("stroke", function(d) {
return d.line_color;
})
.style("stroke-width", function(d) {
return Math.sqrt(d.value)+1;
});
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter()
.append("g")
.attr("transform", function(d){return "translate("+d.x+","+d.y+")"})
.call(force.drag);
node.append("circle")
.attr("class", "node")
.attr("r", 15)
.style("fill", function(d) { return d.fill_color; })
.on("click", function(d){
alert("You clicked on node " + d.name);
});
node.append("title")
.text(function(d) { return d.name; });
node.append("image")
.attr("xlink:href", function(d) { return d.image_url })
.attr("x", -8)
.attr("y", -8)
.attr("width", 26)
.attr("height", 26);
node.append("text")
.attr("dx", function(d) {
if (d.image_url == "/profile.png"){
return 100;
}
else{
return 16;
}
})
.attr("dy", function(d) {
if (d.image_url == "/profile.png"){
return 100;
}
else{
return ".35em";
}
})
// .attr("dy", ".35em")
.text(function(d) { return d.name });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d){return "translate("+d.x+","+d.y+")"});
});
function getData() {
return {
"nodes":[
{"name":"user1","image_url":"http://upload.wikimedia.org/wikipedia/en/thumb/8/80/Wikipedia-logo-v2.svg/103px-Wikipedia-logo-v2.svg.png","fill_color":"blue","text_color":"black"},
{"name":"user2","image_url":"http://upload.wikimedia.org/wikipedia/en/thumb/8/80/Wikipedia-logo-v2.svg/103px-Wikipedia-logo-v2.svg.png","fill_color":"blue","text_color":"black"},
{"name":"user3","image_url":"http://upload.wikimedia.org/wikipedia/en/thumb/8/80/Wikipedia-logo-v2.svg/103px-Wikipedia-logo-v2.svg.png","fill_color":"blue","text_color":"black"},
{"name":"tag1","image_url":"","fill_color":"blue","text_color":"black"},
{"name":"tag2","image_url":"","fill_color":"blue","text_color":"black"},
{"name":"tag3","image_url":"","fill_color":"blue","text_color":"black"}
],
"links":[
{"source":"tag1","target":"user1","value":1,"line_color":"green"},
{"source":"tag2","target":"user1","value":1,"line_color":"green"},
{"source":"tag3","target":"user1","value":1,"line_color":"green"},
{"source":"tag1","target":"user2","value":1,"line_color":"green"},
{"source":"tag2","target":"user2","value":1,"line_color":"green"}
]
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I've been trying to glue ideas from various d3 examples into what i need, starting with the basic example using miserable.json data and then adding:
use of a key function for the data joins
modifying the underlying graph ala this example
making the links' linkDistance() function depend on an attribute of the graph's links'
adding labels to the nodes ala this example
So far i'm only 3 out of 4: something about using the g elements -- using code taken directly from the Mike's "Labeled Force Layout" example -- breaks things and the nodes aren't drawn. I can make it work if i join circle elements directly, but not if I interpose g elements with attached circles and text elements.
The code below is my best effort at a minimal example. This example works, but does not if I replace the .enter().append("circle") line with the .enter().append("g") lines.
Does anyone know why?
var Width = 200;
var Height = 200;
var Pix2Len = 10;
var color = d3.scale.category10();
var svg = d3.select("body").append("svg")
.attr("width", Width)
.attr("height", Height);
var force = d3.layout.force()
.size([Width, Height])
var graph = {
"nodes":[
{"name":"Myriel","idx":0},
{"name":"Napoleon","idx":1},
{"name":"Mlle.Baptistine","idx":2},
{"name":"Mme.Magloire","idx":3}
],
"links":[
{"source":1,"target":0,"len":1,"idx":"1-0"},
{"source":2,"target":1,"len":4,"idx":"2-1"},
{"source":2,"target":0,"len":8,"idx":"2-0"},
{"source":3,"target":0,"len":10,"idx":"3-0"},
{"source":3,"target":1,"len":4,"idx":"3-1"},
{"source":3,"target":2,"len":6,"idx":"3-2"}
]
}
console.log("data loaded. nnode="+graph.nodes.length+" nlinks="+graph.links.length);
force
.nodes(graph.nodes)
.links(graph.links)
.size([Width, Height])
.linkDistance(function(link) {
// console.log("link: "+link.source.name+' '+link.target.name+' '+link.idx+' '+link.len)
return link.len * Pix2Len})
.on("tick", tick);
var link = svg.selectAll(".link")
.data(graph.links, function(d) {return d.idx; })
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(graph.nodes, function(d) {return d.idx; })
// THIS WORKS
.enter().append("circle").attr("r", 8).style("fill", function(d) { return color(0); });
// BUT THIS DOES NOT
// modeled after http://bl.ocks.org/mbostock/950642
//
//.enter().append("g")
//.attr("class", "node")
//.attr("cx", function(d) { return d.x; })
//.attr("cy", function(d) { return d.y; });
//
//node.append("circle")
//.attr("r", 10)
//.style("fill", function(d) { return color(0); });
//
//node.append("text")
//.attr("dx", 12)
//.attr("dy", ".35em")
//.text(function(d) { return d.name });
// 1. Begin with graph from JSON data
setTimeout(function() {
start();
}, 0);
// 2. Change graph topology
setTimeout(function() {
var new4 = {"name":"CountessdeLo","idx":4}
var new5 = {"name":"Geborand","idx":5}
graph.nodes.push(new4,new5);
var link40 = {"source":4,"target":0,"len":1,"idx":"4-0"};
var link43 = {"source":4,"target":3,"len":4,"idx":"4-3"};
var link50 = {"source":5,"target":0,"len":1,"idx":"5-0"};
var link52 = {"source":5,"target":2,"len":4,"idx":"5-2"};
graph.links.push(link40,link43,link50,link52);
start();
}, 3000);
//3. Change some link lengths
setTimeout(function() {
// force.links().forEach(function(link) {
graph.links.forEach(function(link) {
if (link.idx == '1-0')
{link.len=10; }
else if (link.idx == '3-0')
{link.len=2; }
else if (link.idx == '5-0')
{link.len=10; };
}); // eo-forEach
start();
}, 6000);
function start() {
link = link.data(force.links(), function(d) { return d.idx; });
link.enter().insert("line", ".node").attr("class", "link");
link.exit().remove();
node = node.data(force.nodes(), function(d) { return d.idx;});
node.enter().append("circle").attr("class", function(d) {
// tried with the <g> version above
// node.enter().append("g").attr("class", function(d) {
console.log('start:'+' '+d.name);
return d.idx; }).attr("r", 5).style("fill", function(d) { return color(1); });
node.exit().remove();
force.start();
}
function tick() {
node.attr("cx", function(d) {
// console.log('tick:'+' '+d.name);
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; });
}
//} // eo-ready()
In your code you are setting cx and cy attributes to the g element. g elements does not support any position attributes like x, y or cx, cy. To move the contents of a g element you will have to use the transform attribute.
Your code
var node = svg.selectAll(".node")
.data(graph.nodes, function(d) {return d.idx; })
.enter().append("g")
.attr("class", "node")
.attr("cx", function(d) { return d.x; }) //will not work
.attr("cy", function(d) { return d.y; }); //will not work
Solution
var node = svg.selectAll(".node")
.data(graph.nodes, function(d) {return d.idx; })
.enter().append("g")
.attr("class", "node");
node.append("circle")
.attr("r", 10)
.style("fill", function(d) { return color(0); });
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
Use translate function as below to move group elements.
function tick() {
//Moving <g> elements using transform attribute
node.attr("transform", function(d) { return "translate(" + d.x + "," + 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; });
}
JSFiddle
I'm modifying this example of a collapsible, hierarchical force layout and I'm having trouble changing the nodes from circles to squares (using svg:rect). With my current code, all of the squares appear in the top left hand corner. Otherwise they're behaving relatively normally (I can click on them to expand/collapse the tree).
Here is my CSS styles that I'm using for this and other functions:
.node {
cursor: pointer;
}
.node rect {
fill: skyblue;
stroke: green;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: green;
stroke-width: 1.5px;
}
#container div{
float:left;
}
.chartSpace div{
float:left;
margin-right:screen.width-960px;
}
Here is the code for the force layout:
function forceDirected(){
//Clear SVG
d3.select("svg").remove();
var width = 960,
height = 500,
root;
var force = d3.layout.force()
.size([width, height])
.on("tick", tick);
var svg = d3.select(".chartSpace").append("svg")
.attr("width", width)
.attr("height", height);
var link = svg.selectAll(".link"),
node = svg.selectAll(".node");
d3.json("flare.json", function(flare) {
root = flare;
update();
});
function update() {
var nodes = flatten(root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
force
.nodes(nodes)
.links(links)
.start();
// Update the links…
link = link.data(links, function(d) { return d.target.id; });
// Exit any old links.
link.exit().remove();
// Enter any new links.
link.enter().insert("line", ".node")
.attr("class", "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 the nodes…
node = node.data(nodes, function(d) { return d.id; }).style("fill", color);
// Exit any old nodes.
node.exit().remove();
// Enter any new nodes.
node.enter().append("svg:rect")
.attr("class","node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("width", "6")
.attr("height", "6")
.on("click", click)
.call(force.drag);
}
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("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
// Stroke colors
function strColor(d) {
return d._children ? "green" : d.children ? "green" : "green";
}
// Node colors
function color(d) {
return d._children ? "skyblue" : d.children ? "skyblue" : "skyblue";
}
// Toggle children on click.
function click(d) {
if (!d3.event.defaultPrevented) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update();
}
}
// Returns a list of all nodes under the root.
function flatten(root) {
var nodes = [], i = 0;
function recurse(node) {
if (node.children) node.children.forEach(recurse);
if (!node.id) node.id = ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
}
node.enter().append("svg:rect")
.attr("class","node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
should be
var rectSize = 6;
node.enter().append("svg:rect")
.attr("class","node")
.attr("x", function(d) { return d.x + rectSize/2; })
.attr("y", function(d) { return d.y + rectSize/2; })
.attr("width", rectSize)
.attr("height", rectSize)
rect elements are positioned by setting the location of their upper left hand corner with x and y; circle elements' centers are set with cx and cy.
I am trying to implement fisheye distortion on a force directed graph using d3.js. For some reason, the distortion works on the links but not the nodes. This might be caused by the nodes being g elements, but I am not sure. Any help would be appreciated!
var svg, node, link;
function make_graph() {
// Clear out the div first
$("#display_graph svg:first").remove();
// Sets size
var width = 960,
height = 500;
// Maps groups to colors
var color = d3.scale.category20();
// Select the div and append a svg
svg = d3.select("#display_graph").append("svg")
.attr("width", width)
.attr("height", height);
// Pull json from graph
var json_from_db = $('.graph_json').data('json');
var json_nodes = json_from_db.nodes
var json_links = json_from_db.links
// Sets up the force directed graph
var charge_val = $('#charge').val();
var link_distance_val = $('#link_distance').val();
var force = d3.layout.force()
.charge(charge_val)
.linkDistance(link_distance_val)
.size([width, height])
.nodes(json_nodes)
.links(json_links)
.start();
link = svg.selectAll(".link")
.data(json_links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var font_size_px = $('#font_size').val();
node = svg.selectAll(".node")
.data(json_nodes)
.enter().append("g")
.attr("class", "node")
.style("font-size", font_size_px)
.call(force.drag);
var radius = $('#radius').val();
node.append("circle")
.attr("r", radius)
.style("fill", function(d) { return color(d.group); });
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
$('#fisheye_btn').click( function() {
var glyph_color = $(this).css('color');
// original color is white
orig_color = 'rgb(255, 255, 255)';
if (glyph_color == orig_color) {
$(this).css('color', 'orange');
} else {
$(this).css('color', orig_color);
};
});
};
function make_fisheye() {
var fisheye = d3.fisheye.circular()
.radius(200)
.distortion(2);
svg.on("mousemove", function() {
fisheye.focus(d3.mouse(this));
node.each(function(d) { d.fisheye = fisheye(d); })
.attr("cx", function(d) { return d.fisheye.x; })
.attr("cy", function(d) { return d.fisheye.y; })
.attr("r", function(d) { return d.fisheye.z * 4.5; });
link.attr("x1", function(d) { return d.source.fisheye.x; })
.attr("y1", function(d) { return d.source.fisheye.y; })
.attr("x2", function(d) { return d.target.fisheye.x; })
.attr("y2", function(d) { return d.target.fisheye.y; });
});
}
Eventually, I want the user to be able to turn the distortion on or off by clicking a button. Thanks for your help!
I updated the code. I can turn the distortion on or off with a button click now.
// Make force directed graph on button click
$(document).ready( function() {
$("#refresh_btn").click( function() {
make_graph();
});
});
// Turn on and off fisheye distortion
$(document).ready( function() {
$('#fisheye_btn').click( function() {
var glyph_color = $(this).css('color');
// original color is white
orig_color = 'rgb(255, 255, 255)';
if (glyph_color == orig_color) {
$(this).css('color', 'orange');
make_graph();
make_fisheye();
} else {
$(this).css('color', orig_color);
make_graph();
};
});
});
var svg, node, link;
function make_graph() {
// Clear out the div first
$("#display_graph svg:first").remove();
// Sets size
var width = 960,
height = 500;
// Maps groups to colors
var color = d3.scale.category20();
// Select the div and append a svg
svg = d3.select("#display_graph").append("svg")
.attr("width", width)
.attr("height", height);
// Pull json from graph
var json_from_db = $('.graph_json').data('json');
var json_nodes = json_from_db.nodes
var json_links = json_from_db.links
// Sets up the force directed graph
var charge_val = $('#charge').val();
var link_distance_val = $('#link_distance').val();
var force = d3.layout.force()
.charge(charge_val)
.linkDistance(link_distance_val)
.size([width, height])
.nodes(json_nodes)
.links(json_links)
.start();
link = svg.selectAll(".link")
.data(json_links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var font_size_px = $('#font_size').val();
node = svg.selectAll(".node")
.data(json_nodes)
.enter().append("g")
.attr("class", "node")
.style("font-size", font_size_px)
.call(force.drag);
var radius = $('#radius').val();
node.append("circle")
.attr("r", radius)
.style("fill", function(d) { return color(d.group); });
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
};
function make_fisheye() {
var fisheye = d3.fisheye.circular()
.radius(200)
.distortion(2);
svg.on("mousemove", function() {
fisheye.focus(d3.mouse(this));
node.each(function(d) { d.fisheye = fisheye(d); })
.attr("cx", function(d) { return d.fisheye.x; })
.attr("cy", function(d) { return d.fisheye.y; })
.attr("r", function(d) { return d.fisheye.z * 4.5; });
link.attr("x1", function(d) { return d.source.fisheye.x; })
.attr("y1", function(d) { return d.source.fisheye.y; })
.attr("x2", function(d) { return d.target.fisheye.x; })
.attr("y2", function(d) { return d.target.fisheye.y; });
});
}
Unfortunately, I am still having issues with just the links being distorted. The nodes are actually g elements, which I am becoming more and more convinced might be the problem. Is there a way to select elements inside the g element using d3.js? I am surprised that the fisheye distortion is not just passed on to the elements inside the g element. Maybe my guess is wrong?
New Code
Now both the nodes and links are distorted on a button click! Here is the code:
// Global variables
var svg, node, link;
var fisheye_on = false;
// Make force directed graph on button click
$(document).ready( function() {
$("#refresh_btn").click( function() {
// Make sure fisheye is turned off
$('#fisheye_btn').css('color', 'white');
fisheye_on = false;
make_graph();
});
});
// Turn on and off fisheye distortion
$(document).ready( function() {
$('#fisheye_btn').click( function() {
var glyph_color = $(this).css('color');
// original color is white
orig_color = 'rgb(255, 255, 255)';
if (glyph_color == orig_color) {
$(this).css('color', 'orange');
fisheye_on = true;
make_graph();
} else {
$(this).css('color', orig_color);
fisheye_on = false;
make_graph();
};
});
});
function make_graph() {
// Clear out the div first
$("#display_graph svg:first").remove();
// Sets size
var width = 960,
height = 500;
// Maps groups to colors
var color = d3.scale.category20();
// Select the div and append a svg
svg = d3.select("#display_graph").append("svg")
.attr("width", width)
.attr("height", height);
// Pull json from graph
var json_from_db = $('.graph_json').data('json');
var json_nodes = json_from_db.nodes
var json_links = json_from_db.links
// Sets up the force directed graph
var charge_val = $('#charge').val();
var link_distance_val = $('#link_distance').val();
var force = d3.layout.force()
.charge(charge_val)
.linkDistance(link_distance_val)
.size([width, height])
.nodes(json_nodes)
.links(json_links)
.start();
link = svg.selectAll(".link")
.data(json_links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var font_size_px = $('#font_size').val();
node = svg.selectAll(".node")
.data(json_nodes)
.enter().append("g")
.attr("class", "node")
.style("font-size", font_size_px)
.call(force.drag);
var radius = $('#radius').val();
node.append("circle")
.attr("r", radius)
.style("fill", function(d) { return color(d.group); });
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
if (fisheye_on == false) {
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
} else {
var fisheye = d3.fisheye.circular()
.radius(200)
.distortion(2);
svg.on("mousemove", function() {
fisheye.focus(d3.mouse(this));
node.each(function(d) { d.fisheye = fisheye(d); });
node.selectAll("circle")
.attr("cx", function(d) { return d.fisheye.x - d.x; })
.attr("cy", function(d) { return d.fisheye.y - d.y; })
.attr("r", function(d) { return d.fisheye.z * 4.5; });
node.selectAll("text")
.attr("dx", function(d) { return d.fisheye.x - d.x; })
.attr("dy", function(d) { return d.fisheye.y - d.y; });
link.attr("x1", function(d) { return d.source.fisheye.x; })
.attr("y1", function(d) { return d.source.fisheye.y; })
.attr("x2", function(d) { return d.target.fisheye.x; })
.attr("y2", function(d) { return d.target.fisheye.y; });
});
};
};
Unfortunately, I have come across another issue. There seems to be an initial lag when the distortion is applied. The nodes get distorted first and then the links get distorted. Not sure what is causing this. Any hints?
Public JSFiddle
I managed to replicated the issue in a public JSFiddle:
http://jsfiddle.net/cspears2002/vVL99/
In order to make the graph, just click "Make Graph". Clicking "Fisheye" will turn the fisheye distortion on and off. The issue is that the nodes are distorted first and then the links follow. I want nodes and links to be distorted at the same time. Any help would be appreciated!