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
Related
I am using D3's forced layout to display a graph. Now, I require that the nodes change their positions when any node is clicked.
I looked up other related StackOverflow questions but that didn't help me.
The code for render is as follows :
var render = function(graph){
/* var loading = svg.append("text")
.attr("x", width / 2)
.attr("y", height / 2)
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Simulating. One moment please…");*/
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links);
//Enter phase for links.
link.enter().append("line");
//Update phase for links
link
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.nodes,function(d){return d.name;});
//Enter phase for nodes
var node_g = node.enter()
.append("g")
.attr("class","node")
.on("dblclick",nodeClick)
.call(force.drag);
//Update phase for nodes
node_g.append("text")
.attr("class","NodeLabel")
.text(function(d){
return d.name;
});
var nodeCirlce = node_g.append("circle");
nodeCirlce
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
node_g.append("title")
.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_g.attr("transform",function(d){
return "translate("+ d.x+","+ d.y+")";
});
//TODO : Add attr change for node text as well.
});
And the code for the node click handler looks like this :
var nodeClick = function(d,i){
//Translate the graph to center around the selected node.
var x_trans = x_cent - d.x;
var y_trans = y_cent - d.y;
var nodes = oldGraph.nodes;
for(var i=0;i<nodes.length;i++){
var node = nodes[i];
node.x = node.x + 1000;
node.y = node.y + 1000;
node.fixed = true;
}
//oldGraph.nodes = updateNodes(nodes,oldGraph.links);
render(oldGraph);
//setTimeout(function(){layout("json/HUMAN-1g.json");},000);
};
However, the node positions don't get updated.
After changing the data, you need to run the code that updates the positions in the DOM. This is exactly what you have in the tick event handler 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_g.attr("transform",function(d){
return "translate("+ d.x+","+ d.y+")";
I recommend pulling this out into a separate function and then setting it as the tick handler function and calling it from your nodeClick() function. To be clear, you don't need to call render() from the nodeClick() function.
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 will try to make a resume of my problem and give some additional information if someone have a lead for me.
I want to display the history of an element in an application. How it have been construct and what it have done. So it could have 1+ parents, 1+ brothers and 1+ children. It's like a genealogy tree but it could have 3 parents and parents and children can be mixed to give others children. So I thought about a network, and I ended with this :
http://jsfiddle.net/ggrwc8p6/3/
var data = {
"nodes":[
{"name":"1-STS","group":1},
{"name":"2-STS","group":1},
{"name":"1-ADN","group":2},
{"name":"2-ADN","group":2},
{"name":"3-ADN","group":2},
{"name":"4-ADN","group":2}
],
"links":[
{"source":0,"target":2,"value":1},
{"source":1,"target":2,"value":1},
{"source":2,"target":3,"value":5},
{"source":3,"target":4,"value":5},
{"source":3,"target":5,"value":5}
]
};
var width = 500,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(120)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
function makeIt(error, graph) {
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", function(d) { return "node " + d.group; })
.attr("r", 15)
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
node.append("title")
.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; });
});
}
makeIt("",data);
My problem here are :
How can I make this like a tree ? From top to bottom and not with no hierarchy like here ?
How can I put some text in dot (title) and lines ?
Thank for the people that will help me.
++
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!
I'm trying to implement the expand / collapse functionality as i saw on the D3 site, and what i get is that some nodes get to have multiple labels when i run this functionality.
var width = 960,
height = 500;
var node,
path,
root, nodes, links, link;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force();
var resource = $.getJSON('js/nodes.json', function(data)
{
root = restructure(data);
render();
})
function restructure(data)
{
var root = data[0];
data.splice(0,1);
root.children = data;
_.each(root.children, function(child)
{
child._children = child.links;
})
return root;
}
function flatten(root) {
// return root;
var nodes = [];
var links = [];
function rec(node) {
var sourceNode = addNode(nodes, node);
if (node.children)
{
//sourceNode.children = [];
node.children.forEach(function(child)
{
var targetNode = rec(child);
links.push({source:sourceNode, target:targetNode});
//sourceNode.children.push(targetNode);
})
}
return sourceNode;
}
rec(root);
return {nodes:d3.values(nodes), links:links};
}
function addNode(collection, node) {
if(collection[node.name] != null)
return collection[node.name];
collection[node.name] = node;
return node;
}
function render()
{
var flat = flatten(root);
var nodes = flat.nodes;
var links = flat.links;
nodes = tree.nodes(elements.nodes).reverse();
links = tree.links(nodes);*/
force.nodes(nodes)
.links(links)
.size([width, height])
.linkDistance(160)
.charge(-1500)
.on("tick", tick)
.start();
var drag = force.drag()
.on("dragstart", dragstart);
function dragstart(d) {
d.fixed = true;
d3.select(this).classed("fixed", true);
}
function isRoot(node)
{
return node.id == root.id
}
/* link = svg.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("class", "link");*/
// Update the links…
link = svg.selectAll("line.link")
.data(force.links(), function(d) { return d.target.id; });
// 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; })
.style("stroke-width",function(l)
{
console.log(l.source.rank + ':' + l.target.rank);
var val = Math.min(10,Math.max(1,Math.log(Math.max(Math.abs(l.source.rank),Math.abs(l.target.rank)))));
console.log('width: ' + val);
return val;
});
// Exit any old links.
link.exit().remove();
node = svg.selectAll(".node")
.data(force.nodes());
node.exit().remove();
node.enter().append("g")
.attr("class", "node")
.attr("id", function(d) { return d.id; })
.on("click", click)
.on("dblclick", doubleclick)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.call(force.drag);
node.append("image")
.attr("xlink:href", function (d)
{
return "../img/icon-location.png";
})
.attr("x", -8)
.attr("y", -8)
.attr("width", 16)
.attr("height", 16);
/* node.append("circle")
.attr("r", function(d)
{
return isRoot(d)? 14:8;
})
.style("fill",function(d)
{
return isRoot(d)? "steelblue":"";
});*/
var center = svg.select('#node_' + nodes[0].id)
center.append("circle")
.attr("r", "14")
.style("fill","steelblue");
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
node.transition()
.attr("r", function(d) { return d.children ? 4.5 : Math.sqrt(d.size) / 10; });
// Exit any old nodes.
node.exit().remove();
}
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 + ")"; });
}
// Toggle children on click.
function doubleclick(d) {
alert(d);
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
render();
}
function mouseover() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 16);
}
function mouseout() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 8);
}
however, what i get (after expanding) looks like that:
<g class="node fixed" id="1063" transform="translate(329.44373878271944,118.27604414379978)" r="NaN"><image xlink:href="../img/icon-location.png" x="-8" y="-8" width="16" height="16"></image><text x="12" dy=".35em">PRO</text><image xlink:href="../img/icon-location.png" x="-8" y="-8" width="16" height="16"></image><text x="12" dy=".35em">Dropbox</text></g>
any help?
You want to make sure that updated nodes are correctly matched to the existing DOM elements, which you're already doing for the edges. You'll pass an identity function as the second parameter to the node data() call. Perhaps:
node = svg.selectAll(".node")
.data(force.nodes(), function(d) { return d.id; });
The one thing you need to look out for is that this function can be called both on new data elements as well as the stored data on nodes being updated.
For more information see http://bost.ocks.org/mike/selection/