I have a force layout built using d3.js with nodes entering at interval of 15 seconds. When the nodes enter, they enter from any random direction before settling in the center. I want the node to enter always from left top (0,0) and then go to the center to settle.
that.force = d3.layout.force()
.charge(-8.6)
.linkDistance(2)
.size([600, 600]);
that.svg = d3.select(that.selector).append("svg")
.attr("width", that.width)
.attr("height", that.height);
d3.json("data/tweets.json", function(error, graph) {
that.graph = graph;
setInterval(function () {
d3.json("data/tweets.json", function(error, graph) {
that.graph = graph;
that.render();
});
},15000);
that.force
.nodes(that.graph.nodes)
.links(that.graph.links)
.start(0);
that.nodes = that.svg.selectAll("g")
.data(that.graph.nodes)
.enter()
.append("g")
.call(that.force.drag);
that.nodes
.append("rect")
.attr("class", "node")
.attr("width", that.rectw)
.attr("height", that.recth)
.style("fill","white")
.style("stroke", function(d) { return d.COLOR; })
.style("stroke-width", "1px")
that.nodes.append("title")
.text(function(d) { return d.SCREEN_NAME; });
that.nodes.append("image")
.attr("class", "node-image")
.attr("transform", "translate(1,1)")
.attr("xlink:href", function(d) { return "img/"+d.PROFILE_PIC;})
.attr("height", that.rectw-2 + "px")
.attr("width", that.recth-2 + "px");
that.link = that.svg.selectAll(".link")
.data(that.graph.links)
.enter()
.append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
that.force.on("tick", function() {
that.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; });
that.nodes.attr("transform", function(d) {
return "translate("+d.x+","+d.y+")";
})
});
});
Any suggestion will be helpful.
Thanks.
You can pre-set the position of the nodes before giving them to the force layout by setting the x and y attributes of the data. So in your case, all you need to do is initialise x and y of all nodes to 0.
Related
I am trying to add label to the nodes in a D3 force layout, but there seems to be some issue with it. All the text just shows up on top of the screen, overlapping on each other. Here is the code snippet:
var link = g.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line");
var node = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 2.5)
.on('click', clicked);
var text = g.append("g")
.attr("class", "labels")
.selectAll("text")
.data(graph.nodes)
.enter().append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
Elements piling up on the top left corner of the SVG (which is the origin) is a symptom of several problems, among them:
NaN for the positions
Not setting the positions
Not translating the element
Not including the element in the tickfunction
As you have a force directed chart, the last one is the obvious reason.
Solution: You have to include the text in your ticked function.
function ticked() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
text.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
I am visualising a graph of relationships between people with d3. All nodes are connected to a single central node, and then have relationships with other nodes. I've got the basics working, but I'm struggling to work out how to set the parameters like linkDistance, linkStrength, gravity and charge.
Each edge has a rating from 0-5, which I'm then using to compute linkDistance using an inverse linear scale. The main problem is getting the relationships to be represented properly. The central node seems to be much further away than any other node, even though it has the shortest linkDistance with some other nodes. I'm also finding it difficult to find the right settings to get nodes to be an appropriate distance apart.
var h = 500, w = 1000
var color = d3.scale.category20()
var svg = d3.select("body")
.append("svg")
.attr({ height: h, width: w })
queue()
.defer(d3.json, "nodes.json")
.defer(d3.json, "links.json")
.await(makeDiag);
function makeDiag(error, nodes, links, table) {
links = links.filter(function(link) {
if (link.value) return true
})
var scale = d3.scale.linear().domain([0,5]).range([20,0])
var edges = svg.selectAll("line")
.data(links)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1)
/* Establish the dynamic force behavor of the nodes */
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([w,h])
.linkDistance(function(d) {
if (d.value == 0) return null
console.log('in',d.value,'out',scale(d.value))
return scale(d.value)
})
.charge(-1400)
.start();
/* Draw the edges/links between the nodes */
var texts = svg.selectAll("text")
.data(nodes)
.enter()
.append("text")
.attr("fill", "black")
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.text(function(d) { return d.name; });
var nodes = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", function(d,i) { return 20 })
.attr("opacity", 0.7)
.style("fill", function(d,i) { return color(i); })
.call(force.drag);
/* Draw the nodes themselves */
/* Run the Force effect */
force.on("tick", function() {
edges.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; });
nodes.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
texts.attr("transform", function(d) {
return "translate(" + (d.x - 12.5) + "," + (d.y + 5) + ")";
});
});
};
jsFiddle
full screen result
I'm trying to modify Mike's Force-Directed Graph example to use rectangles instead of circles as nodes. Also, I want text inside the rectangle.
I have rectangles showing up with text correctly, however they're not attached to the links, and they do not move.
Here's a codepen link: http://codepen.io/anon/pen/gpgWaz
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);
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("g")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.call(force.drag);
node.append("rect")
.attr("class", "node")
.attr("width", 100)
.attr("height", 35)
.style("fill", function(d) {
return color(d.group);
})
.style("stroke", "black")
.style("stroke-width", "1px");
node.append("text")
.text(function(d) {
return d.name;
})
.style("font-size", "12px")
.attr("dy", "1em");
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("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
});
Update
Thanks to Lars's comment, and his codepen it works now.
Updates I made to my code:
Add transform illustrated by Lars
Changed links to connect at the center of rectangles
Added rounded corners to rectangles
Gave the text a slight margin indentation
Changed to use window width/height
Here my new codepen: http://codepen.io/anon/pen/bdgREd
var width = window.innerWidth,
height = window.innerHeight,
nodeWidth = 100,
nodeHeight = 35;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-1500)
.linkDistance(100)
.friction(0.5)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
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("g")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.call(force.drag);
node.append("rect")
.attr("class", "node")
.attr("width", nodeWidth)
.attr("height", nodeHeight)
.attr("rx", 5)
.attr("ry", 5)
.style("fill", function(d) {
return color(d.group);
})
.style("stroke", "black")
.style("stroke-width", "1px");
node.append("text")
.attr("x", 5)
.attr("y", 2)
.text(function(d) {
return d.name;
})
.style("font-size", "12px")
.attr("dy", "1em");
node.append("title")
.text(function(d) {
return d.name;
});
force.on("tick", function() {
link.attr("x1", function(d) {
return d.source.x + (nodeWidth / 2);
})
.attr("y1", function(d) {
return d.source.y + (nodeHeight / 2);
})
.attr("x2", function(d) {
return d.target.x + (nodeWidth / 2);
})
.attr("y2", function(d) {
return d.target.y + (nodeHeight / 2);
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
You're setting the x and y attributes on the g elements to change their positions -- this won't do anything. You need to set the transform attribute instead, like you're doing when adding the g elements. So your tick handler function would contain
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
instead of
node.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
Complete demo here.
I want to display one <circle> and <text> for each node. My code looks like this, having added the suggested code from the answer below. Please note the different
var width = 960,
height = 500;
var color = d3.scale.category10();
var nodes = [],
links = [];
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.charge(-250)
.linkDistance(25)
.size([width, height])
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var node = svg.selectAll(".node")
.data(force.nodes(), function(d) { return d.id;}),
link = svg.selectAll(".link");
var text=svg.selectAll("text") //simply add text to svg
.data(force.nodes())
.enter()
.append("text")
.attr("class", "nodeText")
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "red");
function start() {
link = link.data(force.links(), function(d) { return d.source.id + "-" + d.target.id; });
link.enter().insert("line", ".node")
.attr("class", function(d) { return "link " + d.edgeType; })
.attr("id", function(d) { return d.source.id + "-" + d.target.id; });
link.exit().remove();
v1: <line>s exist and are displayed, no <circle>s or <text> exist
var g = node.enter().append("g");
g.append("circle")
.attr("class", function(d) { return "node " + d.id; })
.attr("id", function(d) { return d.id; })
.attr("r", 8)
.on("click", nodeClick);
g.append("text")
.text(function(d) {return d.id; });
/v1
v2: <line>s and <circle>s exist and are displayed. <text>s exist within <circle>s but aren't displayed
node = node.data(force.nodes(), function(d) { return d.id;});
node.enter()
.append("circle")
.attr("class", function(d) { return "node " + d.id; })
.attr("id", function(d) { return d.id; })
.attr("r", 8)
.on("click", nodeClick);
node.append("text")
.text(function(d) {return d.id; });
/v2
node.exit().remove();
force.start();
}
function nodeClick() {
var node_id = event.target.id;
handleClick(node_id, "node");
}
function tick() {
text.attr("dx", function(d) { return d.x+5; })
.attr("dy", function(d) { return d.y+5; })
.text(function(d){return d.id});
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; });
}
Simply add text element as following in your js
var text=svg.selectAll("text") //simply add text to svg
.data(Your_nodes_array)
.enter()
.append("text")
.attr("class", "nodeText")
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "red");
And inside tick do this :
text.attr("dx", function(d) { return d.x+5;}) //to keep it away from node
.attr("dy", function(d) { return d.y+5; })
.text(function(d){return d.id});
This will solve circle and text problems as this time we are adding text directly now
I'm not an expert in d3 (as many of you know) however I am attempting to modify a working force network so that the nodes are fixed. So far I have been able to get the nodes to work but haven't been able to get the lines between them to work.
I've created a fiddle at http://jsfiddle.net/PatriciaW/Dnnve/
Any help will be very welcome.
var width = 960,
height = 700,
n = 100;
var categoryColour = {
"community group": "red",
"volunteer": "blue",
"organization": "green",
"air": "transparent"
};
var json2 = {"nodes":[{"node":{"name":"TCAN","x2":1,"y2":2,"description":"A network of organizations in Toronto devoted to climate change mitigation and adaptation.","category":"organization","size":3,"URL":"http:\/\/localhost:8888\/mydrupal\/groups\/tcan"}},{"node":{"name":"Rita ","x2":5,"y2":3,"description":"Rita is devoted to mitigating climate change and participates in many organizations.","category":"volunteer","size":2,"URL":"http:\/\/localhost:8888\/mydrupal\/groups\/rita"}},{"node":{"name":"Green 13","x2":5,"y2":4,"description":"Green 13","category":"community group","size":2,"URL":"http:\/\/localhost:8888\/mydrupal\/groups\/green-13"}},{"node":{"name":"ZCO","x2":3,"y2":1,"description":"Zero Carbon Ontario","category":"organization","size":2,"URL":"http:\/\/localhost:8888\/mydrupal\/groups\/zco"}},{"node":{"name":"Resilient Toronto","x2":3,"y2":5,"description":"","category":"organization","size":3,"URL":"http:\/\/localhost:8888\/mydrupal\/groups\/resilient-toronto"}},{"node":{"name":"City of Toronto","x2":3,"y2":3,"description":"","category":"organization","size":5,"URL":"http:\/\/localhost:8888\/mydrupal\/groups\/city-toronto"}}]};
var nodes=json2.nodes.map(function(json2) {
return json2.node;
});
var i = 0;
while (i < nodes.length) {
nodes[i].fixed=true;
nodes[i].x = (nodes[i].x2)*100;
nodes[i].y = (nodes[i].y2)*100;
i = i+ 1;
}
var json = {"connections":[{"connection":{"source":"Rita","target":"Resilient Toronto"}},{"connection":{"source":"TCAN","target":"Resilient Toronto"}},{"connection":{"source":"Resilient Toronto","target":"City of Toronto"}},{"connection":{"source":"Rita","target":"ZCO"}},{"connection":{"source":"Rita","target":"Green 13"}},{"connection":{"source":"Green 13","target":"TCAN"}},{"connection":{"source":"ZCO","target":"TCAN"}}]};
var links=json.connections.map(function(json) {
return json.connection;
});
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
svg = d3.select("#network")
.append("svg")
.attr("width", width)
.attr("height", height);
var link = svg.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle") // this is OK if use nodes or links below but defaults to links
.attr("r", 8)
.style("fill", function(nodes) {
return categoryColour [nodes.category];
})
node.append("text") // OK
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.name; });
function tick() {
link
.attr("x1", function(d) {
// console.log("d"); console.log(d); has source and target but not .x and y values?
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 + ")";
});
};
// Use a timeout to allow the rest of the page to load first.
setTimeout(function() {
force.tick();
console.log("timeout nodes "); console.log(nodes); //
svg.selectAll("line")
.data(links)
.enter().append("line")
.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; });
svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 4.5);
}, 10);
I'm not an expert either but I think you are having issues because the source and target values in your connections should be referencing the respective positional array index for the node (not the name of the node).
A simple example, in your fiddle just change the first connection from...
{"connection":{"source":"Rita","target":"Resilient Toronto"}}
to...
{"connection":{"source":1,"target":4}}
1 and 4 being the index of the elements in the nodes array. Hope this is what you are looking for.
I updated the jsfiddle with the working code. Needs improvement but demonstrates the principle.