Related
Hello dear Stackoverflow community,
I've come far with my force-directed graph and am really enjoying the project. Yet I keep failing when it comes to entering text elements on the nodes. Please find two images attached, one with my current nodes and one with the labels I'd like to have.
Source: https://bl.ocks.org/mbostock/1093130
Below you'll find the code snippet I'd like to include:
nodeEnter.append("text")
.attr("dy", ".35em")
.text(function(d) { return d.name; });
I've added a //text comment in the area where I have tested the code above.
I've tried a group element in svg.selectAll & .append("circle"). This however broke the visual. What am I missing?
Thank you for your support and happy weekend!
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="https://kit.fontawesome.com/01fcb12790.js">//fontawesome for searchbar
</script>
<script>
var width = 960,
height = 960,
root;
// d3 overview: https://bost.ocks.org/mike/d3/workshop/#121
var force = d3.layout.force()
.size([width, height])
.linkDistance(100) //How far apart the nodes are
.charge(-50) //how far are the group nodes from each other
.gravity(0.001) //how far away are the nodes from each other
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var link = svg.selectAll(".link"),
node = svg.selectAll(".node");
d3.json("readme.json", function(error, json) {
if (error) throw error;
root = json;
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("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 || 10.5; })
.style("fill", color)
.on("click", click)
.call(force.drag);
}
//text
//creating the tick function from the force variable
//the "e" paramater can be used for positioning
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._children ? "#E83E1D" : d.children ? "#c6dbef" : "#fd8d3c";
}
// 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;
}
// Tag animation begin.
//Tag animation end
</script>
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.
I am trying to give different shape or image icons for different nodes.
Like, for Target node i.e dark blue you can see in the js fiddle I want to give as a rectangle to an image icon and for a lighter circle with different shape or image icon.I have followed this Link also but cant able to make it work.
Here is the js fiddle like:
https://jsfiddle.net/AmitSah/bp6p3p92/
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
radius = 10;
/*svg.call(d3.zoom().on("zoom", function () {
svg.attr("transform", d3.event.transform);
}));*/
var color = d3.scaleOrdinal(d3.schemeCategory20);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.node_id; }).strength(1) )
//.force("charge", d3.forceManyBody())
.force("collide", d3.forceCollide(radius + 1).iterations(4))
.force("center", d3.forceCenter(width / 2, height / 2));
// projection definition
var projection = d3.geoAlbers()
.scale(1280)
.translate([width / 2, height / 2]);
// path generator definition for major cities, including point radius
var path = d3.geoPath()
.projection(projection)
.pointRadius(2);
// draws the states
d3.json("https://raw.githubusercontent.com/LogicalInsightscg/Web-Mobile-Dashboard/master/us_new.json", function(error, us_new) {
if (error) return console.error(error);
svg.selectAll(".states")
.data(topojson.feature(us_new, us_new.objects.states).features)
.enter().append("path")
.attr("class", function(d) { return "states " + d.id; })
.attr("d", path);
// adding state boundaries
svg.append("path")
.datum(topojson.mesh(us_new, us_new.objects.states))
.attr("d", path)
.attr("class", "state-boundary");
d3.json("https://raw.githubusercontent.com/LogicalInsightscg/Web-Mobile-Dashboard/master/nodesandlinks.json", function(error, graph) {
if (error) throw error;
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.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:line")
.attr("d", "M0,-5L10,0L0,5");
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.referrals); })
.attr("marker-end", "url(#end)");
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); })
//.attr("fixed", true)
.attr("cx", function(d) {
return projection([d.longitude, d.latitude])[0];
})
.attr("cy", function(d) {
return projection([d.longitude, d.latitude])[1];
})
/* .call(d3.drag()
.on("end", dragended)); */
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
node.attr('fx', function(d) { return projection([d.longitude, d.latitude])[0]; })
.attr('fy', function(d) { return projection([d.longitude, d.latitude])[1]; });
link.attr('x1', function(d) { return projection([d.source.longitude, d.source.latitude])[0]; })
.attr('y1', function(d) { return projection([d.source.longitude, d.source.latitude])[1]; })
.attr('x2', function(d) { return projection([d.target.longitude, d.target.latitude])[0]; })
.attr('y2', function(d) { return projection([d.target.longitude, d.target.latitude])[1]; });
node.append("title")
.text(function(d) { return d.node_name; });
function ticked() {
link
.attr("distance", 10)
// .attr("x1", function(d) { return d.source.x; })
// .attr("y1", function(d) { return d.source.y; })
.attr('x1', function(d) { return projection([d.source.longitude, d.source.latitude])[0]; })
.attr('y1', function(d) { return projection([d.source.longitude, d.source.latitude])[1]; })
.attr('x2', function(d) { return projection([d.target.longitude, d.target.latitude])[0]; })
.attr('y2', function(d) { return projection([d.target.longitude, d.target.latitude])[1]; });
node.attr('cx', function(d) {if (d.group > 0) { return projection([d.longitude, d.latitude])[0]; }
else { return d.x; }})
.attr('cy', function(d) {if (d.group > 0) { return projection([d.longitude, d.latitude])[1]; }
else { return d.y; }});
}
/* function ticked() {
link.attr('x1', function(d) { return projection([d.source.longitude, d.source.latitude])[0]; })
.attr('y1', function(d) { return projection([d.source.longitude, d.source.latitude])[1]; })
.attr('x2', function(d) { return projection([d.target.longitude, d.target.latitude])[0]; })
.attr('y2', function(d) { return projection([d.target.longitude, d.target.latitude])[1]; });
node.attr('r', width/100)
.attr('cx', function(d) { return projection([d.longitude, d.latitude])[0]; })
.attr('cy', function(d) { return projection([d.longitude, d.latitude])[1]; });
} */
});
});
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;
}
Can anyone help me to do this?
EDITED:
Kindly see this image:
Network Map
d3.selectAll(".company").append("rect")
.attr("width", 20)
.attr("height", 20)
.data(someData)
.attr("x", function(d) {
if (d.cat == 2)
{
return projection([d.longitude, d.latitude])[0];
}
})
.attr("y", function(d) {
if (d.cat == 2)
{
return projection([d.longitude, d.latitude])[1];
}
});
Thanks,
Amit Sah
The easiest way would be to have something in the data that you can use to set the attributes, i.e.
.attr('stroke-color', function(){
if
(d.dataAtt = someAttribute, return black)
else if
(d.dataAtt = otherAttribute, return white)
Or if you want to set the target nodes to a different colour, you could use d3.select to select all the node id's in the target link array, then change the attributes of only those nodes. Setting the node id might help select the correct nodes (see the bottom line):
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 5)
.attr("id", d.graph.nodes.id)//or something
Edit:
var node = svg.append("g")
.attr("class", function(){
if
(d.dataAtt = someAttribute, return Aclass)
else if
(d.dataAtt = otherAttribute, return Bclass))//it would be easiest to separate the nodes by class
.data(graph.nodes)
.enter()
.attr("id", d.graph.nodes.id)//or something
d3.selectAll(".Aclass")
.append("rect")
d3.selectAll(".Bclass")
.append("circle")
You could use the source/target array to select the nodes where there id is in one or the other, but without seeing your data, I don't know if that would be appropriate.
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