Related
I'm trying to upgrade the working D3.js v3 collapsible graph code below to work under D3.js v5. I've changed layout.force() to use the new forceSimulation function as shown further below.
var width = 960,
height = 500,
root;
var force = d3.layout.force()
.size([width, height])
.linkDistance(100)
.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("graph2.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;
}
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; })
.attr("marker-end", "url(#arrow)");
// 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();
}
I've changed the force block to:-
var force = d3.forceSimulation()
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2))
.force("link", d3.forceLink().id(function (d) { return d.id))
.on("tick", tick);
but can't figure out what else needs to change. It doesn't draw anything.
Thanks.
I have a force graph with simple nodes and link. Each node is a svg file 500x500.
I implemented zoom and dragging.
When clicked on a node, that node
become fixed while other is released
The problem is there are weird popping line and black shape if I drag a node and then panning around.
I do not know how to put it in words so I record this video. I used Firefox:
https://drive.google.com/file/d/0B2W405T4XZ7ZZDlLRkc0RE01NG8/view?usp=sharing
Edit: I added the json
{
"nodes":[
{"name":"Mohamed","type":"CEO"},
{"name":"Google.Inc","type":"Company"},
{"name":"Apple","type":"Company"},
{"name":"Leonardo Decaprio","type":"CEO"},
{"name":"Prisoner A","type":"Prisoner"},
{"name":"National Bank of Hakuna","type":"Bank"},
{"name":"Shanghai Major Bank","type":"Bank"},
{"name":"Boeing 747","type":"Boat"},
{"name":"Company Ltd.","type":"Company"},
{"name":"Shanghai Major Bank","type":"Bank"},
{"name":"Boeing 747","type":"Boat"}
],
"links":[
{"source":0,"target":1,"value":1},
{"source":1,"target":3,"value":1},
{"source":2,"target":0,"value":8},
{"source":3,"target":0,"value":10},
{"source":3,"target":2,"value":6},
{"source":3,"target":4,"value":1},
{"source":5,"target":0,"value":1},
{"source":6,"target":0,"value":1},
{"source":7,"target":0,"value":1},
{"source":8,"target":0,"value":2},
{"source":3,"target":1,"value":1}
]}
Edit: I added the source code as per requested.
var force = d3.layout.force()
.charge(-1000)
.linkDistance(80)
.gravity(.05)
.size([width, height]);
var drag = force.drag()
.origin(function(d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
var zoom = d3.behavior.zoom()
.scaleExtent([0.1, 10])
.on("zoom", zoomed);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom);
var container = svg.append("g");
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
d3.select(this).attr("x", d.x = d3.event.x).attr("y", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
d3.json("pst.json", function(error, graph) {
if (error) throw error;
root=graph.nodes[0];
root.x = width / 2;
root.y = height / 2;
root.fixed = true;
var link = container.append("g").selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = container.append("g").selectAll(".node")
.data(graph.nodes)
.enter()
.append("image")
.attr("xlink:href",function(d){return d.type+ ".svg" ;})
.attr("class", "node")
.attr("x", function(d) { return d.x+12; })
.attr("y", function(d) { return d.y+12; })
.attr("width", "24px")
.attr("height", "24px")
.call(force.drag)
.on("click",click);
force
.nodes(graph.nodes)
.links(graph.links)
.start();
node.append("title")
.text(function(d) { return d.name; });
function click(node) {
root.fixed = false;
root=node;
root.fixed=true;
}
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-12; })
.attr("y", function(d) { return d.y-12; });
});
});
My force directed graph is drawn correctly. But it doesn't stay still. I slightly moves here and there on the svg sometimes some nodes disappear from the visibility leaving clusters of nodes here and there. This is how the graph initially looks:
Some time later it looks like this: nodes have gone every where away from the div
var graph = new Object();
var map = new Object();
var index = 0;
var linkIndex = 0;
var width = $("#d3graph").width();
var height = $("#d3graph").height() ;
var svg = d3.select("#d3graph").append("svg:svg")
.attr("width", width)
.attr("height", height);
// tool tip with the label
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function (d) {
return d.name + "";
})
svg.call(tip);
/* I take nodes and edges from outside. That part works fine*/
graph.links = dataset2;
graph.nodes = dataset1;
function drapGraph(graph) {
svg.selectAll("g.link").remove();
svg.selectAll("g.gnode").remove();
var force = self.force = d3.layout.force()
.nodes(graph.nodes)
.links(graph.links)
.gravity(.05)
.distance(30)
.charge(-120)
.size([width, height])
.start();
//map radius domain--> range
var rScale = d3.scale.linear()
.domain([d3.min(graph.nodes, function (d) {
return Math.log(d.group);
}), d3.max(graph.nodes, function (d) {
return Math.log(d.group);
})])
.range([0, 30]);
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", 2)
.style("stroke-length", function (d) {return (10000/d.value);});
var node = svg.selectAll("g.gnode")
.data(graph.nodes)
.enter().append("g")
.attr("class", "gnode")
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.call(force.drag);
var maxretweets = d3.max(graph.nodes, function (d) {
return Math.log(d.group);
});
var minretweets = d3.min(graph.nodes, function (d) {
return Math.log(d.group);
});
var maxContent = d3.max(graph.nodes, function (d) {
return d.degree;
});
var minvalue = d3.min(graph.links, function (d) {
return d.value;
});
var circle = node.append("circle")
.attr("r", function (d) {
return rScale(Math.log(d.group));
})
.style("fill", function (d) {
return d.color;
})
.style("stroke", "#000000")
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.call(force.drag);
//give you nodes with labels
var label = node.append("text")
.style("font-family", "sans-serif")
.style("text-anchor", "middle")
.style("font-size", "8")
.style("stroke", "#404040")
.text(function (d) {
if (rScale(Math.log(d.group)) > 5) {
return d.name;
}
});
force.on("tick", function () {
node.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
});
circle.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
});
label.attr("x", function (d) {
return d.x;
})
.attr("y", 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;
});
});
svg.selectAll("g").attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y;
});
}
Can someone help me to solve this problem? There is a small problem here but I couldn't figure out it, I tried so many things but it still doesn't work.
I would suggest to use bounded x and y values to fix such issues.
Try calculating x and y positions in tick function as shown below.
node.attr("cx", function(d) { return d.x = Math.max(radius, Math.min(width - radius, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(radius, Math.min(height - radius, d.y)); });
Refer here for a sample.
Edit: There is no need to update the circle and text positions individually since they are grouped. Just need to update the group elements and links as follows.
force.on("tick", function () {
svg.selectAll("g.node")
.attr("transform", function (d) {
d.x = Math.max(radius, Math.min(width - radius, d.x));
d.y = Math.max(radius, Math.min(height - radius, d.y));
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;
});
});
}
How can I display the source nodes names of a clicked target node in a forced directed graph using D3?
The snippet below is from some example code for directed graphs by Mike Bostock. How can I go about modifying this base code so when a node is clicked on the graph, the target values of that node is displayed on the screen (in this case I would like to display the name attributes)?
For example the 0-th element in "nodes" is:
"nodes":[
{"name":"Myriel","group":1},
...
]
And in "links" the targets are define like so:
"links":[
{"source":1,"target":0,"value":1}, // Napoleon
{"source":2,"target":0,"value":8}, // Mlle.Baptistine
{"source":3,"target":0,"value":10}, // Mme.Magloire
{"source":3,"target":2,"value":6},
{"source":4,"target":0,"value":1}, // CountessdeLo
{"source":5,"target":0,"value":1}, // Geborand
{"source":6,"target":0,"value":1}, // Champtercier
{"source":7,"target":0,"value":1}, // Cravatte
{"source":8,"target":0,"value":2}, // Count
{"source":9,"target":0,"value":1}, // OldMan
...
{"source":11,"target":0,"value":5}, // Valjean
...
]
Then clicking on the node Myriel would display:
Napoleon,Mlle.Baptistine,Mme.Magloire,CountessdeLo,Geborand,Champtercier,Cravatte,Count,OldMan,Valjean
Myriel is located here in the graph:
Below is the JavaScript code:
var width = 960,
height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
d3.json("https://gist.githubusercontent.com/mbostock/4062045/raw/9653f99dbf6050b0f28ceafbba659ac5e1e66fbd/miserables.json", function(error, graph) {
if (error) throw error;
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", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(force.drag)
.on("click",function(d){
var targets = graph.links.filter(function(i){
return i.target.name == d.name
});
tip.show( targets.map(function(i){ return i.source.name;}) );
});
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; });
});
});
var width = 960,
height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
d3.json("https://gist.githubusercontent.com/mbostock/4062045/raw/9653f99dbf6050b0f28ceafbba659ac5e1e66fbd/miserables.json", function(error, graph) {
if (error) throw error;
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", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(force.drag)
.on("click",function(d){
var targets = graph.links.filter(function(i){
return i.target.name == d.name
});
tip.show( targets.map(function(i){ return i.source.name;}) );
});
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; });
});
});
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.0/d3.min.js"></script>
Add a click event on each node, then inside we can get all the targets by filtering the graph.links array such that we only have the elements who's target.name is the same as the clicked nodes name d.name. Once we have that you can use .map() to return the array with .source.name to give the name of those items inside targets:
.on("click",function(d) {
var targets = graph.links.filter(function(i){
return i.target.name==d.name;
});
tip.show( targets.map(function(i){ return i.source.name; }) );
});
var tip = d3.tip().attr('class', 'd3-tip').html(function(d) { return d; });
var width = 960,
height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.call(tip);
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
d3.json("https://gist.githubusercontent.com/mbostock/4062045/raw/9653f99dbf6050b0f28ceafbba659ac5e1e66fbd/miserables.json", function(error, graph) {
if (error) throw error;
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", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(force.drag)
.on("click",function(d){
var targets = graph.links.filter(function(i){
return i.target.name == d.name
});
tip.show( targets.map(function(i){ return i.source.name;}) );
});
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; });
});
});
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.6.7/d3-tip.min.js"></script>
Now for easy display of those values the d3-tip library can be used. This would be initialized for the graph like so:
var tip = d3.tip().attr('class', 'd3-tip').html(function(d) { return d; });
...
var svg = ..
svg.call(tip);
And finally the tip.show(...) function in the first code snippet will display those items on the graph.
I am a newbie to d3 and trying to do a graph layout.
var w = 1000;
var h = 500;
var dataset = {
nodes: [{
name: 'Aden'
}, {
name: 'Bob'
}, {
name: 'Sue'
}],
edges: [{
source: 0,
target: 1
}, {
source: 1,
target: 2
}]
};
var svg = d3.select("body")
.append("svg")
.attr("height", h)
.attr("width", w);
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w, h])
.linkDistance([50])
.start();
var nodes = svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.attr("r", 10)
.style("fill", "red")
.call(force.drag);
var edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
My fiddle is in : http://jsfiddle.net/abhimita/MnX23/
I don't see any graph but couldn't figure out what I am doing incorrectly. Any help will be really appreciated.
1.you neet to set cx and cy of circle to position the circle
2.you need to set x1 y1, x2 y2 of line to position line
3.if you need active you need to listen to tick event of force layout
var w = 300;
var h = 300;
var dataset = {
nodes: [{
name: 'Aden'
}, {
name: 'Bob'
}, {
name: 'Sue'
}],
edges: [{
source: 0,
target: 1
}, {
source: 1,
target: 2
}]
};
var svg = d3.select("body")
.append("svg")
.attr("height", h)
.attr("width", w);
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w, h])
.on("tick", tick) // listener tick to listen position change
.linkDistance([50])
.start();
var nodes = svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.attr("r", 10)
// set cx cy of circle to position the circle
.attr("cx", function (d) {return d.x; })
.attr("cy", function (d) { return d.y; })
.style("fill", "red")
.call(force.drag);
var edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
// set x1, y1, x2, y2 to position the 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;
})
.style("stroke", "#ccc")
.style("stroke-width", 1);
// make it actively
function tick(e) {
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; });
}
demo update: http://jsfiddle.net/MnX23/3/
In circle you have to mention the cx and cy attributes and line x1,y1,x2,y2 attributes
The x1 attribute defines the start of the line on the x-axis
The y1 attribute defines the start of the line on the y-axis
The x2 attribute defines the end of the line on the x-axis
The y2 attribute defines the end of the line on the y-axis
Try this code:
DEMO
var w = 1000;
var h = 500;
var dataset = {
nodes: [{
name: 'Aden'
}, {
name: 'Bob'
}, {
name: 'Sue'
}],
edges: [{
source: 0,
target: 1
}, {
source: 1,
target: 2
}]
};
var svg = d3.select("body")
.append("svg")
.attr("height", h)
.attr("width", w);
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w, h])
.linkDistance([50])
.start();
var nodes = svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.attr("r", 10)
.style("fill", "red")
.call(force.drag);
var edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
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; });
});