I am trying to move my nodes (and links) by force in d3 graph. The nodes add dynamically to the graph. I've looked through pile of examples, but they are based on d3 outdated version with force.layout instead of forceSimulation.
I've tried many options and cases and nothing is working, the browser draws circle in the upper left part of svg.
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="./index.css" type="text/css">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script type="text/javascript" src="graph.js"></script>
</head>
<body>
<div id="graph">
<script>
var graph = new myGraph("#graph");
graph.addNode("A");
graph.addNode("B");
graph.addLink("A", "B");
</script>
</div>
</body>
</html>
graph.js
function myGraph(el) {
var graph = this.graph = {
"nodes":[{"name": "a"}],
"links":[{"source":0,"target":1}]
};
this.addNode = function (name) {
graph["nodes"].push({"name":name});
update();
}
var findNode = function (name) {
for (var i in graph["nodes"]) if (graph["nodes"][i]["name"] === name) return graph["nodes"][i];
}
this.addLink = function (source, target) {
graph["links"].push({"source":findNode(source),"target":findNode(target)});
update();
}
var vis = d3.select(el).append("svg:svg")
.attr("width", 578)
.attr("height", 300);
var nodes = vis.selectAll("circle.node")
.data(graph.nodes);
var links = vis.selectAll("line.link")
.data(graph.links);
var force = d3.forceSimulation(nodes)
.force("charge", d3.forceManyBody().strength(-10))
.force("link", d3.forceLink(links))
.force("center", d3.forceCenter());
var update = function () {
var link = vis.selectAll("line.link")
.data(graph.links);
link.enter().insert("line")
.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; });
link.exit().remove();
var node = vis.selectAll("circle.node")
.data(graph.nodes);
node.enter().append("circle")
.attr("class", "node")
.attr("r", 10)
.attr("x", "-8px")
.attr("y", "-8px");
node.append("text")
.attr("class", "nodetext")
.attr("dx", 12)
.attr("dy", ".25em")
.text(function(d) { return d.name });
node.exit().remove();
force.on("tick", move);
function move() {
links.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; })
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"});
};
force.restart();
}
update();
}
Link to jsFiddle with my code
There should be node and link variables (instead of all nodes and links) in move function, but browser produces error in this case:
d3.v4.min.js:4 Error: attribute transform: Expected number, "translate(undefined,undefiā¦".
Help me, please, to make it running like it should be!
Related
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
</style>
<body>
</body>
<script src="d3.v3.min.js"></script>
<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);
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); });
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function(d) {return d.r;})
.style("fill", function(d) { return color(d.group); })
node.append("title")
.text(function(d) { return d.name; });
node.append("text")
.text("A");
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; });
});
});
</script>
</html>
The code above is using D3js to draw a Force-directed graph drawing from some data, and I just want to place some text on the circle so I use node.append("text") you can see it above.
But however when add it it does not work, there is still not text on the circle so I wonder how could it be????
SVG does not allow a text element inside an circle element. You should put the circle and the text element inside a common g. Try something like this (not tested):
var node = svg.selectAll(".node")
.data(graph.nodes).enter().append('g').classed('node', true);
node.append("circle")
.attr("r", function(d) {return d.r;})
.style("fill", function(d) { return color(d.group); })
.append("title")
.text(function(d) { return d.name; });
node.append("text")
.text("A");
And then instead of setting cx and cy on nodes, set the transform property on the g.node:
force.on("tick", function() {
// ...
node.attr("transform", function(d) { return 'translate(' + [d.x, d.y] + ')'; })
});
I found this d3 code, and I am unable to put text to the nodes. I have seen some solutions to create a parent class for this but I am getting errors trying them out. The current code works but doesn't add text :(
Here's the plnkr link for the working code -
http://plnkr.co/edit/dqAaEhJnnK4i2nBsTaNu?p=preview
Also, this is the code
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
</style>
<body>
<button id="clusterButton" type="button">Cluster</button>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="../netClustering.js"> </script>
<!-- <script type="text/javascript" src="groupInABox.js"> </script> -->
<script>
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
// var gb = GroupInABox(force, "");
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("miserables.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); });
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.call(force.drag);
// node.append("title")
// .text(function(d) { return d.name; });
node.append("text")
.attr("dx", 12)
.attr("dy", "0.35em")
.text(function(d){ return d.name; });
// .call(force.drag);
// .attr("font-size", function(d){ return d.influence*1.5>9? d.influence*1.5: 9; })
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; });
});
d3.select("#clusterButton").on("click", function () {
netClustering.cluster(graph.nodes, graph.links);
svg.selectAll(".node").transition().duration(500).style("fill", function(d) { return color(d.cluster); });
});
});
</script>
You are appending a text to circle as child node. That will not render. A circle can't have a text. There are 2 ways, either define a new enter selection with the same data and append texts ,or create a g element append circle and text and on tick,vupdate transform attribute each tick. I did the first one, change the below part of your script and it will render:
// node.append("title")
// .text(function(d) { return d.name; });
var texts =svg.selectAll(".texts")
.data(graph.nodes)
.enter()
.append("text")
.attr("dx", 12)
.attr("dy", "0.35em")
.text(function(d){ return d.name; });
// .call(force.drag);
// .attr("font-size", function(d){ return d.influence*1.5>9? d.influence*1.5: 9; })
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; });
texts.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
});
You entire html:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
</style>
<body>
<button id="clusterButton" type="button">Cluster</button>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="netClustering.js"> </script>
<!-- <script type="text/javascript" src="groupInABox.js"> </script> -->
<script>
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
// var gb = GroupInABox(force, "");
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("miserables.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); });
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.call(force.drag);
var texts =svg.selectAll(".texts")
.data(graph.nodes)
.enter()
.append("text")
.attr("dx", 12)
.attr("dy", "0.35em")
.text(function(d){ return d.name; });
// .call(force.drag);
// .attr("font-size", function(d){ return d.influence*1.5>9? d.influence*1.5: 9; })
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; });
texts.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
});
d3.select("#clusterButton").on("click", function () {
netClustering.cluster(graph.nodes, graph.links);
svg.selectAll(".node").transition().duration(500).style("fill", function(d) { return color(d.cluster); });
});
});
</script>
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;
});
});
}
I am having some issues with d3js and I can't figure out what is going on. The idea is to draw initial graph from some endpoint data (first img), that's fine works well. Each node is clickable, on click ajax call is made for that node and data is returned, based on some criteria at that point nodes.push(xx), links.push(xx) happens to add new nodes and restart() is called to draw new nodes and links. The issue is that the main graph is doing the correct thing (Not showed on screenshots as I had to put fake data on the first graph i.e. calling an endpoint /record/id/first doesn't return a data) but there are bunch of random nodes showing up in the right bottom corner.
You can also see on the example below, even if the data doesn't change after clicking on first/second/third something wrong goes with node.enter() after restart() with the same data passed in...
JS FIDDLE: http://jsfiddle.net/5754j86e/
var w = 1200,
h = 1200;
var nodes = [];
var links = [];
var node;
var link;
var texts;
var ids = [];
var circleWidth = 10;
var initialIdentifier = "marcin";
nodes = initialBuildNodes(initialIdentifier, sparql);
links = initialBuildLinks(sparql);
//Add SVG
var svg = d3.select('#chart').append('svg')
.attr('width', w)
.attr('height', h);
var linkGroup = svg.append("svg:g").attr("id", "link-group");
var nodeGroup = svg.append("svg:g").attr("id", "node-group");
var textGroup = svg.append("svg:g").attr("id", "text-group");
//Add Force Layout
var force = d3.layout.force()
.size([w, h])
.gravity(.05)
.charge(-1040);
force.linkDistance(120);
restart();
function restart() {
force.links(links)
console.log("LINKS ARE: ", links)
link = linkGroup.selectAll(".link").data (links);
link.enter().append('line')
.attr("class", "link");
link.exit().remove();
force.nodes(nodes)
console.log("NODES ARE: ", nodes)
node = nodeGroup.selectAll(".node").data (nodes);
node.enter().append("svg:g")
.attr("class", "node")
.call(force.drag);
node.append('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', circleWidth )
.attr('fill', function(d, i) {
if (i>0) { return palette.pink }
else { return palette.blue }
})
.on("click", function(d) {
nodeClicked (d);
})
.on('mouseenter', function(d){
nodeMouseEnter(d)
})
.on('mouseout', function(d){
nodeMouseOut(d)
});
node.exit().remove();
var annotation = textGroup.selectAll(".annotation").data (nodes);
annotation.enter().append("svg:g")
.attr("class", "annotation")
.append("text")
.attr("x", function(d) { return d.radius + 4 })
.attr("y", ".31em")
.attr("class", "label")
.text(function(d) { return d.name; });
annotation.exit().remove();
force.start();
}
function nodeClicked (d) {
// AJAX CALL happens here and bunch of nodes.push({name: "new name"}) happen
}
force.on('tick', function(e) {
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, i) {
return 'translate('+ d.x +', '+ d.y +')';
})
svg.selectAll(".annotation").attr("transform", function(d) {
var labelx = d.x + 13;
return "translate(" + labelx + "," + d.y + ")";
})
});
Okay I got it, based on the docs (https://github.com/mbostock/d3/wiki/Selections#enter):
var update_sel = svg.selectAll("circle").data(data)
update_sel.attr(/* operate on old elements only */)
update_sel.enter().append("circle").attr(/* operate on new elements only */)
update_sel.attr(/* operate on old and new elements */)
update_sel.exit().remove() /* complete the enter-update-exit pattern */
From my code you can see I do enter() and then once again I add circle on node in a separate statement.
node = nodeGroup.selectAll(".node").data (nodes);
node.enter().append("svg:g")
.attr("class", "node")
.call(force.drag);
node.append('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', circleWidth )
.attr('fill', function(d, i) {
if (i>0) { return palette.pink }
else { return palette.blue }
});
Adding circle should be within the scope of enter() otherwise it happens to all nodes not only the new nodes therefore it should be :
node.enter().append("svg:g")
.attr("class", "node")
.call(force.drag)
.append('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', circleWidth )
.attr('fill', function(d, i) {
if (i>0) { return palette.pink }
else { return palette.blue }
});
I'm playing with the following Javascript + SVG + D3 code.
http://bl.ocks.org/1095795
The code is here:
https://gist.github.com/1095727
In short, I need unique images for each node (instead of the duplicated smiley face image as it's pulling now), but I'm not savvy enough to make it happen. Any help would be appreciated.
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.layout.js"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.geom.js"></script>
<style type="text/css">
.link { stroke: #ccc; }
.nodetext { pointer-events: none; font: 10px sans-serif; }
</style>
</head>
<body>
<script type="text/javascript">
var w = 960,
h = 500
var nodes = [],
links = [];
var vis = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h);
var force = self.force = d3.layout.force()
.nodes(nodes)
.links(links)
.gravity(.05)
.distance(100)
.charge(-100)
.size([w, h]);
force.on("tick", function() {
var node = vis.selectAll("g.node")
.data(nodes, function(d) { return d.id;} )
var link = vis.selectAll("line.link")
.data(links, function(d) { return d.source.id + ',' + d.target.id})
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 recalc() {
var link = vis.selectAll("line.link")
.data(links, function(l) { return l.source.id + '-' + l.target.id; });
link.enter().append("svg:line")
.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; });
link.exit().remove();
var node = vis.selectAll("g.node")
.data(nodes, function(d) { return d.dpid;}).call(force.drag);
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.call(force.drag);
nodeEnter.append("svg:image")
.attr("class", "circle")
.attr("xlink:href", "https://d3nwyuy0nl342s.cloudfront.net/images/icons/public.png")
.attr("x", "-8px")
.attr("y", "-8px")
.attr("width", "16px")
.attr("height", "16px");
nodeEnter.append("svg:text")
.attr("class", "nodetext")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.id });
node.exit().remove();
force.start();
}
/* Scenario */
/* step 1: add three nodes and three links */
function step1() {
var nA = {id: 'aaa'};
var nB = {id: 'bbb'};
var nC = {id: 'ccc'};
nodes.push(nA);
nodes.push(nB);
nodes.push(nC);
var lAB = {source: nA, target: nB};
var lAC = {source: nA, target: nC};
var lBC = {source: nB, target: nC};
links.push(lAB );
links.push(lAC);
links.push(lBC);
recalc();
}
/* step 2: node B disappears with links */
function step2() {
nodes = nodes.filter(function(n) { return n.id !== 'bbb'; });
links = links.filter(function(l) { return (l.source.id !== 'bbb' && l.target.id !== 'bbb'); });
recalc();
}
/* step 3: node B reappears with links */
function step3() {
var nB = {id: 'bbb'};
nodes.push(nB);
/* find exiting nodes for links */
var nA = nodes.filter(function(n) { return n.id === 'aaa'; })[0];
var nC = nodes.filter(function(n) { return n.id === 'ccc'; })[0];
var lAB = {source: nA, target: nB};
var lBC = {source: nB, target: nC};
links.push(lAB);
links.push(lBC);
recalc();
}
window.setTimeout(step1, 2000);
window.setTimeout(step2, 4000);
window.setTimeout(step3, 6000);
force.start();
recalc();
</script>
</body>
</html>
It looks like you're trying to add a different image for each node, instead of the one image added here:
nodeEnter.append("svg:image")
.attr("class", "circle")
.attr("xlink:href", "https://d3nwyuy0nl342s.cloudfront.net/images/icons/public.png")
This adds the image at node creation time, which sounds like it would be sufficient for your purposes. All you need to do is supply a function instead of a string for the relevant .attr() call. If you had an array of image URLs to use, you could get them in sequence like this:
nodeEnter.append("svg:image")
.attr("class", "circle")
.attr("xlink:href", function(d, i) {
// d is the node data, i is the index of the node
return myImages[i];
})
or, if you had image URLs in the node data itself:
nodeEnter.append("svg:image")
.attr("class", "circle")
.attr("xlink:href", function(d, i) {
// d is the node data, i is the index of the node
return d.nodeImageUrl;
})