Having an atlas force graph with setup as follow, I would like to zoom in and out on mouse wheel events from anywhere in the drawing area but nodes (circles) in order to allow dragging individual nodes.
var svg = graph.append("svg")
.attr("width", width)
.attr("height", height)
.attr("pointer-events", "all")
.call(d3.behavior.zoom().on("zoom", redraw))
.append('g');
var link = svg.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("svg:g")
.attr("class", "node")
.on("dblclick", dblclick)
.call(force.drag);
node.append("circle")
.attr("class", "circle");
function redraw() {
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
}
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 + ")"; });
});
The issue I have with this code is that clicking on a node and dragging it drags the whole graph, whereas when removing the call(... redraw) part it would let me drag individual nodes.
Is there a way to mix both behaviors and either prevent zooming when pointer is inside a node, or have node event prevalent on global (svg) events?
<!DOCTYPE html>
<html>
<head>
<title>Fidlde</title>
<script type="text/javascript" src="d3-master/d3.v3.min.js"></script>
<style>
.circle {
fill: #F5F5F5;
stroke: #999999;
stroke-width: 3;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
.link {
stroke: #999999;
stroke-opacity: .6;
stroke-width: 3;
}
</style>
</head>
<body>
<div id="graph">Hello!</div>
<script>
// graph size
var width = 400;
var height = 400;
var nodes = [{name: 'A'}, {name: 'B'}, {name: 'C'}, {name: 'D'}];
var edges = [{source: 'A', target: 'B'}, {source: 'B', target: 'C'}, {source: 'C', target: 'A'}, {source: 'C', target: 'D'}];
var nodeMap = {};
nodes.forEach(function(x) { nodeMap[x.name] = x; });
var links = edges.map(function(x) {
return { source: nodeMap[x.source], target: nodeMap[x.target], value: 1 };
});
var graph = d3.select("#graph");
var svg = graph.append("svg")
.attr("width", width)
.attr("height", height)
.attr("pointer-events", "all")
.call(d3.behavior.zoom().on("zoom", redraw))
.append('g');
var force = d3.layout.force()
.gravity(.25)
.distance(140)
.charge(-3500)
.size([width, height]);
/* Issue was here, the following code addresses it.
Thanks to Lars and Cool Blue - see comments
var drag = force.drag()
.on("dragstart", dragstart);
*/
var stdDragStart = force.drag().on("dragstart.force");
force.drag()
.on("dragstart", function(d){
//prevent dragging on the nodes from dragging the canvas
d3.event.sourceEvent.stopPropagation();
stdDragStart.call(this, d);
});
force
.nodes(nodes)
.links(links)
.friction(0.8)
.start();
var link = svg.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("svg:g")
.attr("class", "node")
.on("dblclick", dblclick)
.call(force.drag);
node.append("circle")
.attr("class", "circle")
.attr("r", 10);
node.append("text")
.attr("dx", -4)
.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 + ")"; });
});
// redraw after zooming
function redraw() {
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
}
function dblclick(d) {
d3.select(this).classed("fixed", d.fixed = false);
}
function dragstart(d) {
d3.select(this).classed("fixed", d.fixed = true);
}
</script>
</body>
</html>
In oder to address the remaining dragging node issue, I made the following changes in the code:
node.enter()
.append("svg:g")
.attr("pointer-events", "all")
.attr("id", function(d) { return '_'+d.name })
.attr("class", "node")
.on("click", nodeClick)
.on("dblclick", nodeDoubleClick)
.on("mouseover", nodeMouseOver)
.on("mouseout", nodeMouseOut)
.call(force.drag);
function nodeClick(d) {
// fix the current node to its position
d.fixed = true;
}
function nodeDoubleClick(d) {
// release the current node
d.fixed = false;
}
function nodeMouseOver(d) {
// move the current node to front - some nodes are overlapping each others
var sel = d3.select(this);
sel.moveToFront();
// stop the whole graph
force.stop();
}
function nodeMouseOut(d) {
// resume node motion
force.start();
}
I also removed the following dragstart function which remained from previous code and was probably called while zooming.
/* function dragstart(d) {
d3.select(this).classed("fixed", d.fixed = true);
}
*/
Everything is now properly working. Thank you all for your contributions.
try this snippet of code :) also works
var width=600;
var height=600;
var nodes=[{
"name":"n1"
},{
"name":"n2"
},{
"name":"n3"
},{
"name":"n4"
},{
"name":"n5"
}];
var links=[{"source":0,"target":1},
{"source":0,"target":2},
{"source":0,"target":3},
{"source":1,"target":4},
{"source":2,"target":4},
{"source":3,"target":2}];
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.attr("transform","translate(200,200)");
svg.append("rect")
.attr("width",width)
.attr("height",height)
.attr("fill","none")
.attr("pointer-events","all")
.call(d3.behavior.zoom().on("zoom", redraw));;
var force=d3.layout.force().charge(-400).linkDistance(200);
force.nodes(nodes).links(links).start();
var link = svg.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
var node = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("class","circle")
.call(force.drag);
function redraw() {
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
}
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 + ")"; });
});
Related
I was using d3 version 4.5 earlier in my project for d3 pack circles. Now I have used latest version and got difference in pack layout symmetry. Before Image and After Image
Here is my code in both cases. Want to have same symmetry as it was in earlier version. Is there any new way to get this symmetry in latest version of d3.
var diameter = 250;
var color = d3.scaleOrdinal(d3.schemeCategory20);
var bubble = d3.pack(data)
.size([diameter, 185])
.padding(1.4);
var svg = d3.select("#trending-topic")
.append("svg")
.attr("width", diameter)
.attr("height", 185)
.attr("class", "bubble");
var nodes = d3.hierarchy(data)
.sum(function(d) {
return d.Count;
});
var format = d3.format(",d");
d3.selection.prototype.moveToFront = function() {
return this.each(function() {
this.parentNode.appendChild(this);
});
};
var node = svg.selectAll(".node")
.data(bubble(nodes)
.descendants())
.enter()
.filter(function(d) {
return !d.children;
})
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("title", function(d) {
return d.Name;
});
/* transparent circle with border */
node.append("circle")
.attr("r", function(d) {
return d.r;
//return d.data.Radius;
})
.style("fill", function(d, i) {
return d.data.fillColor;
})
.on("click", function(d) {
getTopicArticle(d.data.tid);
});
node.append("text")
.each(function(d) {
var arr = d.data.Name.split(" ");
for(i = 0; i < arr.length; i++) {
if(arr[i].length > 10) {
arr[i] = arr[i].substring(0, 7) + '...';
}
d3.select(this)
.append("tspan")
.text(arr[i])
.attr("dy", i ? "1.2em" : 0)
.attr("x", 0)
.attr("text-anchor", "middle")
.attr("class", "tspan" + i)
.attr("fill", "white")
.attr("font-size", function(d) {
return d.r / 3;
})
.on("click", function(d) {
getTopicArticle(d.data.tid);
});
}
d3.select(this)
.append("title")
.text(d.data.Name);
});
d3.select(self.frameElement)
.style("height", 185 + "px");
d3.selectAll(".node")
.on("mouseover", function(d) {
var circle = d3.select(this)
.select("circle");
var text = d3.select(this)
.selectAll("tspan");
text.transition()
.duration(500)
.attr("font-size", function(d) {
return d.r;
});
})
.on("mousemove", function(d) {
})
.on("mouseleave", function(d) {
var circle = d3.select(this)
.select("circle");
circle.transition()
.duration(500)
.attr("r", function(d) {
return d.r;
});
var text = d3.select(this)
.selectAll("tspan");
text.transition()
.duration(500)
.attr("font-size", function(d) {
return d.r / 3;
});
});
You were right, it's this commit in d3-hierarchy between 1.1.1 and 1.1.2, which in turn was introduced between d3 4.5.0 and 4.5.1. It addresses this issue, about packing circles more condensely.
I recommend just accepting the changes, but if you really don't want to change the layout, import d3-hierarchy 1.1.1 *after* d3` to override the hierarchy module. This returns the same layout as the older version of d3:
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.1/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-hierarchy/1.1.1/d3-hierarchy.min.js"></script>
The downside is that by making the package versions out of sync, you might break something now or in the future, so it's not a long term sustainable approach.
To test use the following snippet and comment/uncomment the script imports.
var diameter = 250;
var color = d3.scaleOrdinal(d3.schemeCategory20);
var data = {
children: [{
Name: 'Economy',
fillColor: 'grey',
Count: 12
},
{
Name: 'Politics',
fillColor: 'grey',
Count: 10
},
{
Name: 'ESG',
fillColor: 'lightblue',
Count: 5
},
{
Name: 'Tech',
fillColor: 'lightblue',
Count: 5
},
{
Name: 'Leisure',
fillColor: 'pink',
Count: 4
},
{
Name: 'Coronavirus',
fillColor: 'pink',
Count: 4
},
{
Name: 'Blockchain',
fillColor: 'darkblue',
Count: 2,
},
{
Name: 'Sports',
fillColor: 'darkblue',
Count: 2,
},
{
Name: 'Coding',
fillColor: 'purple',
Count: 1,
},
{
Name: 'India',
fillColor: 'purple',
Count: 1,
}
],
};
var bubble = d3.pack(data).size([diameter, 185]).padding(1.4);
var svg = d3.select("#trending-topic")
.append("svg")
.attr("width", diameter)
.attr("height", 185)
.attr("class", "bubble");
var nodes = d3.hierarchy(data)
.sum(function(d) {
return d.Count;
});
var format = d3.format(",d");
d3.selection.prototype.moveToFront = function() {
return this.each(function() {
this.parentNode.appendChild(this);
});
};
var node = svg.selectAll(".node")
.data(bubble(nodes).descendants())
.enter()
.filter(function(d) {
return !d.children
})
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("title", function(d) {
return d.Name;
});
/* transparent circle with border */
node.append("circle").attr("r", function(d) {
return d.r;
//return d.data.Radius;
}).style("fill", function(d, i) {
return d.data.fillColor;
});
node.append("text").each(function(d) {
var arr = d.data.Name.split(" ");
for (i = 0; i < arr.length; i++) {
if (arr[i].length > 10) {
arr[i] = arr[i].substring(0, 7) + '...';
}
d3.select(this).append("tspan")
.text(arr[i])
.attr("dy", i ? "1.2em" : 0)
.attr("x", 0)
.attr("text-anchor", "middle")
.attr("class", "tspan" + i).attr("fill", "white").attr("font-size", function(d) {
return d.r / 3;
});
}
d3.select(this).append("title").text(d.data.Name);
});
d3.select(self.frameElement).style("height", 185 + "px");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.1/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-hierarchy/1.1.1/d3-hierarchy.min.js"></script>
<div id="trending-topic"></div>
This is currently the code looks like however I seem to be unable to print the metric/weight on the network links or the network title on the nodes. If someone could help out that would be great. if you'd like to see the network diagram please send it over a message. I'm using D3.js version 3.
Below is my code:
var width = 1000, height = 700;
var color = d3.scale.category10();
var svg = d3.select("#d3-example").select("svg")
if (svg.empty()) {
svg = d3.select("#d3-example").append("svg")
.attr("width", width)
.attr("height", height);
}
d3.json("graph.json", function(error, graph) {
links = graph.links
var nodes = {};
links.forEach(function(link) {
link.source = nodes[link.source] ||
(nodes[link.source] = {name: link.source});
link.target = nodes[link.target] ||
(nodes[link.target] = {name: link.target});
});
var force = d3.layout.force()
.size([width, height])
.nodes(d3.values(nodes))
.links(links)
.gravity(0.1)
.linkDistance(200)
.on('tick', tick)
.charge(-1000)
.start();
var link = svg.selectAll('.link')
.data(links)
.enter().append('line')
.attr('class', 'link');
link.append("text")
.attr("class", "data text-tooltip1")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("transform", function(d) {
return "translate(" +
((d.source.y + d.target.y)/2) + "," +
((d.source.x + d.target.x)/2) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
console.log(d.target.name);
return d.target.name;
});
var node = svg.selectAll('.node')
.data(force.nodes())
.enter().append('circle')
.attr('class', 'node')
.attr('r', width * 0.02)
node.append("svg:title")
.text(function(d) { return d.name });
function tick(e) {
node.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.call(force.drag);
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; });
};
})
Try the below modifications in your code. This should solve your problem:
var width = 1000, height = 700;
var color = d3.scale.category10();
var svg = d3.select("#d3-example").select("svg")
if (svg.empty()) {
svg = d3.select("#d3-example").append("svg")
.attr("width", width)
.attr("height", height);
}
d3.json("graph.json", function(error, graph) {
links = graph.links
var nodes = {};
links.forEach(function(link) {
link.source = nodes[link.source] ||
(nodes[link.source] = {name: link.source});
link.target = nodes[link.target] ||
(nodes[link.target] = {name: link.target});
});
var force = d3.layout.force()
.size([width, height])
.nodes(d3.values(nodes))
.links(links)
.gravity(0.1)
.linkDistance(200)
.on('tick', tick)
.charge(-1000)
.start();
var link = svg.selectAll('.link')
.data(links)
.enter().append('line')
.attr('class', 'link').attr('id', function(d){return d.id});
d3.selectAll(".link").each(function(d) {
svg
.append('svg:text').attr("class", "LinkLabelText")
.style("font-size", 15)
.style("fill", "teal")
.attr("x", (this.getTotalLength() / 2))
.attr("y", "-20")
.append('textPath')
.attr("xlink:href", "#" + d.id)
.html(function() {
return d.target.name;
});
});
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
console.log(d.target.name);
return d.target.name;
});
var node = svg.selectAll('.node')
.data(force.nodes())
.enter().append('circle')
.attr('class', 'node')
.attr('r', width * 0.02)
node.append("svg:text")
.text(function(d) { return d.name });
function tick(e) {
node.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.call(force.drag);
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; });
};
})
we are using a D3 forced digraph from the following link:
http://bl.ocks.org/jose187/4733747
i was unable to figure out how to enable click on a node. did notice that we have the coordinates but not sure how to "attach" a click event handler.
any ideas would be most appreciated.
In D3, the listeners are attached using selection.on:
Adds or removes a listener to each selected element for the specified event typenames.
So, for a click event, it's simple as this:
node.on("click", function(){
//your code here
}
Check the demo with your code:
<script src="http://d3js.org/d3.v2.min.js?2.9.3"></script>
<style>
.link {
stroke: #aaa;
}
.node text {
stroke:#333;
cursor:pointer;
}
.node circle{
stroke:#fff;
stroke-width:3px;
fill:#555;
}
</style>
<body>
<script>
var width = 400,
height = 300
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.gravity(.05)
.distance(100)
.charge(-100)
.size([width, height]);
var json = {
"nodes":[
{"name":"node1","group":1},
{"name":"node2","group":2},
{"name":"node3","group":2},
{"name":"node4","group":3}
],
"links":[
{"source":2,"target":1,"weight":1},
{"source":0,"target":2,"weight":3}
]
};
force
.nodes(json.nodes)
.links(json.links)
.start();
var link = svg.selectAll(".link")
.data(json.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.weight); });
var node = svg.selectAll(".node")
.data(json.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r","5");
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
node.on("click", function(d){
alert("hello, I'm " + 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 + ")"; });
});
</script>
I have created a d3 force layout, and it works very well. Now I will add a group of data to my graph. I hope I could control the center of my new nodes. For example, supposed the center is (100,100), I hope the new nodes lay out into rectangle area like [(50,50) to (150,150)] as a whole.
var width = 500,
height = 500;
var nodes = [{id:0, n:'Tom'}, {id:1, n:'Join'}, {id:2, n:'John'}, {id:3, n:'Bob'}, {id:4, n:'4'}, {id:5, n:'5'}, {id:6, n:'6'}];
var links = [{source:0,target:1},{source:0,target:2},{source:0,target:3},{source:0,target:4},{source:0,target:5},{source:1,target:5},{source:1,target:6}];
// init force
var force = d3.layout.force()
.charge(-120)
.linkDistance(120)
.size([width, height]);
// init svg
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// set tick function
force.on("tick", function () {
d3.selectAll(".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;
});
// controll the coordinates here
d3.selectAll(".node").attr("transform", function(d){
if(d.flag == 1){
d.x = Math.max(50, Math.min(150, d.x));
d.y = Math.max(50, Math.min(150, d.y));
}
return "translate("+d.x+","+d.y+")";
});
}).on('end', function(){
svg.selectAll(".node").each(function(d){d.fixed=true;});
});
function setData(ns, ls){
var update = svg.selectAll(".link").data(ls);
update.enter().append("line")
.attr("class", "link")
.style("stroke-width", 1);
update.exit().remove();
update = svg.selectAll(".node").data(ns);
update.enter().append("g")
.attr("class", "node")
.attr("id", function(d){return d.id})
.call(force.drag)
.call(function(p){
p.append("image")
.attr("class", "nodeimage")
.attr("width", "30px")
.attr("height", "30px")
.attr("x", "-15px")
.attr("y", "-15px");
p.append("text")
.attr("class", "nodetext")
.attr("dx", "-10px")
.attr("dy", "20px")
.style("font-size", "15px")
.text(function(d){return d.n});
});
update.exit().remove();
update.selectAll(".nodeimage")
.each(function() {
d3.select(this).datum(d3.select(this.parentNode).datum());
})
.attr("xlink:href", function(d){
var img;
if(d.flag == 1){
img = "http://www.gravatar.com/avatar/1eccef322f0beef11e0e47ed7963189b/?default=&s=80"
}else{
img = "http://www.gravatar.com/avatar/a1338368fe0b4f3d301398a79c171987/?default=&s=80";
}
return img;
});
force.nodes(ns)
.links(ls)
.start();
}
//init
setData(nodes, links);
setTimeout(function(){
//generate new data and merge to old data
nodes = nodes.concat(generateNewData());
setData(nodes, links);
//how do i control the coordinate of new nodes?
}, 3000);
function generateNewData(){
var ns = [];
for(var i = 0; i < 10; i++){
ns.push({id:i+100,n:'n'+i,flag:1});
}
return ns;
}
Here is my demo of jsfiddle:http://jsfiddle.net/cs4xhs7s/4/
The latest demo shows that the nodes can display in the rectangle, however, their coordinates are the same. I hope it is an available force layout.
https://jsfiddle.net/wpnq15mf/1/
var width = 500,
height = 500;
var nodes = [{id:0, n:'Tom'}, {id:1, n:'Join'}, {id:2, n:'John'}, {id:3, n:'Bob'}, {id:4, n:'4'}, {id:5, n:'5'}, {id:6, n:'6'}];
var links = [{source:0,target:1},{source:0,target:2},{source:0,target:3},{source:0,target:4},{source:0,target:5},{source:1,target:5},{source:1,target:6}];
// init force
var force = d3.layout.force()
.charge(-500)
.linkDistance(120)
.gravity(0.1)
.size([width, height]);
// init svg
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// set tick function
force.on("tick", function () {
d3.selectAll(".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;
});
// controll the coordinates here
d3.selectAll(".node").attr("transform", function(d){
if(d.flag == 1){
d.x = Math.max(50, Math.min(150, d.x));
d.y = Math.max(50, Math.min(150, d.y));
}
return "translate("+d.x+","+d.y+")";
});
}).on('end', function(){
svg.selectAll(".node").each(function(d){d.fixed=true;});
});
function setData(ns, ls){
var update = svg.selectAll(".link").data(ls);
update.enter().append("line")
.attr("class", "link")
.style("stroke-width", 1);
update.exit().remove();
update = svg.selectAll(".node").data(ns);
update.enter().append("g")
.attr("class", "node")
.attr("id", function(d){return d.id})
.call(force.drag)
.call(function(p){
p.append("image")
.attr("class", "nodeimage")
.attr("width", "30px")
.attr("height", "30px")
.attr("x", "-15px")
.attr("y", "-15px");
p.append("text")
.attr("class", "nodetext")
.attr("dx", "-10px")
.attr("dy", "20px")
.style("font-size", "15px")
.text(function(d){return d.n});
});
update.exit().remove();
update.selectAll(".nodeimage")
.each(function() {
d3.select(this).datum(d3.select(this.parentNode).datum());
})
.attr("xlink:href", function(d){
var img;
if(d.flagx == 1){
img = "http://www.gravatar.com/avatar/1eccef322f0beef11e0e47ed7963189b/?default=&s=80"
}else{
img = "http://www.gravatar.com/avatar/a1338368fe0b4f3d301398a79c171987/?default=&s=80";
}
return img;
});
force.nodes(ns)
.links(ls)
.start();
}
//init
setData(nodes, links);
setTimeout(function(){
//generate new data and merge to old data
nodes = nodes.concat(generateNewData());
links = links.concat(generateNewLinks());
setData(nodes, links);
//how do i control the coordinate of new nodes?
}, 3000);
function generateNewData(){
var ns = [];
ns.push({id:6,n:'n'+i,flag:1, flagx:1});
for(var i = 1; i < 10; i++){
ns.push({id:i+6,n:'n'+i, flagx:1});
}
return ns;
}
function generateNewLinks(){
var ns = [];
ns.push({source:7,target:8});
ns.push({source:7,target:9});
ns.push({source:7,target:10});
ns.push({source:7,target:11});
ns.push({source:7,target:12});
ns.push({source:7,target:13});
ns.push({source:7,target:14});
ns.push({source:7,target:15});
ns.push({source:7,target:16});
return ns;
}
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I've made a force directed graph with d3.js plugin, and I wanna color the nodes and the labels with the different color according to group which they belong.
I've added scale for color:
var color = d3.scale.category20();
and to node variable I've added:
.style("fill", function(d) { return color(d.group); })
but all nodes are in the same color..
Here is my current situation: http://jsfiddle.net/WBkw9/
full script:
var links = [
{source: "John", target: "Mike", group: "5"},
{source: "John", target: "Janice", group: "5"},
{source: "John", target: "Caleb", group: "5"},
{source: "John", target: "Anna", group: "4"},
{source: "John", target: "Tommy", group: "3"},
{source: "John", target: "Jack", group: "2"},
{source: "John", target: "Vilma", group: "1"},
];
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
});
var color = d3.scale.category20();
var width = 960,
height = 500;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
var svg = d3.select("body").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")
.style("fill", function(d) { return color(d.group); })
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.call(force.drag);
node.append("circle")
.attr("r", 8);
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
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 + ")"; });
}
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);
}
what am I missing for different color on each group?
Your problem is that group is not defined for your data. As a result, all of your nodes are colored for group 'undefined'. Your circles are defined for the data in force.nodes(), which have the attributes index name px py weight x and y. group is only defined for the links, which never have color applied to them.
As it currently stands, there also isn't a clear way to determine what color a node should be. What happens if more than one link connects to a node, and these links are in different groups?
Here is my code (based on http://bl.ocks.org/mbostock/4062045). It's working perfectly.
You can see how it looks like here : http://jsfiddle.net/Rom2BE/H2PkT/
Each group has a different color.
**index.html**
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 650,
height = 700;
var color = d3.scale.category10();
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);
d3.json("data.json", function(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); });
// You define here your nodes and the color will be d.group
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
//Display node name when mouse on a node
node.append("title")
.text(function(d) { return d.name; });
//Where and how nodes are displayed
force.on("tick", function() {
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; });
});
//Legend
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
});
</script>
**data.json**
{"nodes":[
{"name":"Vertex 5","group":"Virtuals-MacBook-Pro-36095"},{"name":"Vertex 9","group":"Virtuals-MacBook-Pro-36095"},{"name":"Vertex 15","group":"Virtuals-MacBook-Pro-3-53688"},{"name":"Vertex 20","group":"Virtuals-MacBook-Pro-36095"},{"name":"Vertex 26","group":"Virtuals-MacBook-Pro-4-40842"},{"name":"Vertex 29","group":"Virtuals-MacBook-Pro-36095"},{"name":"Vertex 33","group":"Virtuals-MacBook-Pro-36095"},{"name":"Vertex 37","group":"Virtuals-MacBook-Pro-36095"},{"name":"Vertex 49","group":"Virtuals-MacBook-Pro-3-53688"},{"name":"Vertex 52","group":"Virtuals-MacBook-Pro-4-40842"},{"name":"Vertex 53","group":"Virtuals-MacBook-Pro-4-40842"},{"name":"Vertex 58","group":"Virtuals-MacBook-Pro-36095"},{"name":"Vertex 59","group":"Virtuals-MacBook-Pro-4-40842"},{"name":"Vertex 65","group":"Virtuals-MacBook-Pro-4-40842"},{"name":"Vertex 73","group":"Virtuals-MacBook-Pro-4-40842"},{"name":"Vertex 74","group":"Virtuals-MacBook-Pro-36095"},{"name":"Vertex 80","group":"Virtuals-MacBook-Pro-36095"},{"name":"Vertex 84","group":"Virtuals-MacBook-Pro-4-40842"},{"name":"Vertex 87","group":"Virtuals-MacBook-Pro-4-40842"},{"name":"Vertex 99","group":"Virtuals-MacBook-Pro-4-40842"}
],
"links":[
{"source":5,"value":1,"target":11},{"source":5,"value":1,"target":12},{"source":10,"value":1,"target":12},{"source":11,"value":1,"target":5},{"source":11,"value":1,"target":12},{"source":11,"value":1,"target":14},{"source":12,"value":1,"target":5},{"source":12,"value":1,"target":10},{"source":12,"value":1,"target":11},{"source":14,"value":1,"target":11},{"source":16,"value":1,"target":19},{"source":18,"value":1,"target":19},{"source":19,"value":1,"target":16},{"source":19,"value":1,"target":18}
]}
Your group info is only available in the links object, like #ckersch already pointed out. You would need to add the group info to you nodes object too. For this example that can be done by changing line 16 into:
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target, group: link.group});
But for more complex data, with more than one source, all sources would have the same colour (or would that be OK?).
I made that change in this Fiddle: http://jsfiddle.net/WBkw9/19/.
I think you need to change the style attribute of the circle, not the g element.
node.append("circle").style("fill", function(d) { return color(d.group); })
Edit: The group property in the data must also be changed integers, or cast later.
Did you ever solve this? if not a possible solution is here: http://jsfiddle.net/adeaver/F2fbu/1/
Each group/node is differently colored along with the corresponding text by adding:
.style("fill", function(d) { return color(d.group); })
to the text append and group: link.group to the function that computes the nodes from the links