I am modifying the d3.js Collapsible Force Layout where the nodes are given as circles.
http://bl.ocks.org/mbostock/1062288
I changed it to a group g and attached the circle into the g. The problem is that in the tick function when I change from
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
to
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
the nodes are all displaced.
I have changed the reference to node so node refers to the group g in the second case.
I have created force layouts and have done this a lot of times but never faced this problem.
Is it because of the d3.tree.layout? I don't know. Please help.
I have tried a few more times with other svg elements like text and rect and I found out that the problem occurs only when there is circle involved and I need to give attributes like cx and cy to text and rect even though these elements don't have these attributes, to make them work.
So please help someone. Totally confused.
Code::
var link = svg.selectAll(".link"),
node = svg.selectAll(".node");
link = link.data(force.links());
// Exit any old links.
link.exit().remove();
// Enter any new links.
link.enter().insert("line",".node")
.attr("class", "link")
.attr("x1", function(d,i) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
// Update the nodes…
node = node.data(force.nodes(), function(d) { return d.id; }).style("fill", color);
// Exit any old nodes.
node.exit().remove();
// Enter any new nodes.
node.enter().append("g")
.attr("class", "node")
.attr("title",function(d) { return d.name || d.layout; })
.on("click", click)
.call(force.drag);
node.append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return 4.5; })
.style("fill", color);
node.append("text")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("fill","red")
.attr("stroke","red")
.style("font-size","9px")
.on("click",click)
.text(function(d){return d.name})
.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; });
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
Here's the code that generates the faulty graph.
The problem is that you're setting the coordinates twice; on the actual elements and on the g containing them:
node.append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return 4.5; })
.style("fill", color);
(similarly for the text elements) and
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
This causes the offset. To fix, just set one of them (the translation on the g elements):
node.append("circle")
.attr("r", function(d) { return 4.5; })
.style("fill", color);
node.append("text")
.attr("fill","red")
.attr("stroke","red")
// etc
Related
I am trying to wrap text which is parsed from a json response into my application.
I have two svg elements : rect and text. I want to find a way where I can wrap my svg text such that it fits in the rectangle.
( Picture Force layout with text labels and rectangles )
This is the link to the original project in which I have made modifications
Visualizing Reddit Discussions
setupGraph();
function setupGraph() {
$(".network").empty();
names = {};
nodecolor = {};
force = d3.layout.force()
.charge(-500)
.linkDistance(20)
.size([width, height]);
nodes = force.nodes(),
links = force.links();
force.on("tick", function() {
svg.selectAll("line.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("rect.node")
.attr("x", function(d) {
return d.x - d.curWidth / 2;
})
.attr("y", function(d) {
return d.y - d.curHeight / 2;
});
svg.selectAll("text.node")
.attr("x", function(d) {
return d.x - d.curWidth / 2 + 8;
})
.attr("y", function(d) {
return d.y - d.curHeight / 2 + 20;
});
});
d3.select("svg").remove();
svg = d3.select("#chart").append("svg:svg")
.attr("width", width)
.attr("height", height)
.attr("class", "network");
}
function updateNetwork() {
var link = svg.selectAll("line.link")
.data(links, function(d) {
return d.source.id + "-" + d.target.id;
});
link.enter().insert("svg:line", "text.node", "rect.node")
.attr("class", "link")
.style("stroke-width", function(d) {
return 2;
})
.style("stroke", "gray")
.style("opacity", 0.1);
var node = svg.selectAll("rect.node")
.data(nodes, function(d) {
return d.id;
});
var node = svg.selectAll("text.node")
.data(nodes, function(d) {
return d.id;
});
var nodeEnter = node.enter().append("svg:rect")
.attr("class", "node")
.call(force.drag)
.attr("width", function(d) {
return d.curWidth;
})
.attr("height", function(d) {
return d.curHeight;
})
.style("opacity", 1.0)
.on("mouseover", displayTooltip)
.on("mousemove", moveTooltip)
.on("mouseout", removeTooltip)
.on("mouseover", function(d) {
d3.select(this).transition().attr("height", 100).attr("width", 100); //.style("fill", "red");
})
.call(force.drag)
var nodeEnterr = node.enter().append("svg:text")
.attr("class", "node")
.text(function(d) {
return d.name + ": " + d.body
})
.call(force.drag)
.style("opacity", 1.0)
.on("mouseover", displayTooltip)
.on("mousemove", moveTooltip)
.on("mouseout", removeTooltip)
.call(force.drag)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I am trying to connect SVG circles and rectangles by drawing lines between source circles and target rectangles. Here is the format of my json file:
[{"sourceNode":"1","type":"sourceNode"},
{"sourceNode":"3","type":"sourceNode"},
{"sourceNode":"8","type":"sourceNode"},
.....
{"targetNode":"1","type":"targetNode"},
{"targetNode":"7","type":"targetNode"},
{"targetNode":"1","type":"targetNode"},
.....
{"type":"link","source":"1","target":"2"},
{"type":"link","source":"3","target":"4"},
{"type":"link","source":"3","target":"5"}]
I am using a tick function to give attributes to circles and the line. The circles work just fine, but I don't get lines with no attributes when I inspect my SVG in html.
Here is the code :
var nodeSource = g.selectAll("circle")
.data(data.filter(function (d){ return d.type == "sourceNode"; }))
.enter().append("circle")
.attr("r", 5)
.style("fill", "blue")
.call(force.drag);
var nodeTarget = g.selectAll("rect")
.data(data.filter(function (d){ return d.type == "targetNode"; }))
.enter().append("rect")
.attr("width", 10)
.attr("height", 10)
.style("fill", "green")
.call(force.drag);
var link = g.selectAll("line")
.data(data.filter(function (d){ return d.type == "link"; }))
.enter().append("line")
.style("stroke-width", "2")
.style("stroke", "grey")
.call(force.drag);
function tick(e) {
nodeSource
.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)); });
nodeTarget
.attr("x", function(d) { return d.x = Math.max(radius(), Math.min(width() - radius(), d.x)); })
.attr("y", function(d) { return d.y = Math.max(radius(), Math.min(height() - radius(), 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; });
chart.draw()
}
When you're assigning x1, x2, etc. coordinates to your line, you're not actually giving them a value. In your json file, the data for links has:
"source": "1"
for example. Using d.source.anything won't return a value because "1" doesn't have any properties attached to it. If you want to get a reference to the node which has this number, you have to use d3 to find it:
line.attr('x1', function (d) {
return d3.selectAll('circle').filter(function (k) {
return d.source === k.sourceNode;
}).attr('cx');
})
Then, when you want to do the target nodes:
line.attr('x2', function(d) {
return d3.selectAll('rect').filter(function (k) {
return d.target === k.targetNode;
}).attr('x');
})
This is my code look like, you can also have full code on JsFiddle .
I want to have labels on every node, but I can't.
By the way, labels can be embedded in the circle in the console.
var nodes = svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.attr("r", 10)
.style("fill", function(d, i){
return colors(i);
})
.call(force.drag);
var label = nodes.append("svg:text")
.text(function (d) { return d.name; })
.style("text-anchor", "middle")
.style("fill", "#555")
.style("font-family", "Arial")
.style("font-size", 12);
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; });
label.attr("x", function(d){ return d.x; })
.attr("y", function (d) {return d.y - 10; });
});
Right now, you are appending the text elements to the circle elements, and that simply won't work.
When you write...
var label = nodes.append("svg:text")
You're appending the texts to the nodesselection. However, you have to keep in mind what nodes is:
var nodes = svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
Thus, you are appending the texts to circles, and that doesn't work. They show up when you inspect the page (as <circle><text></text></circle>), but nothing will actually show up in the SVG.
Solution: just change to:
var label = svg.selectAll(null)
.data(dataset.nodes)
.enter()
.append("text")
.text(function (d) { return d.name; })
.style("text-anchor", "middle")
.style("fill", "#555")
.style("font-family", "Arial")
.style("font-size", 12);
Here is the fiddle: https://jsfiddle.net/gerardofurtado/7pvhxfzg/1/
I don't understand how to display text on my nodes
I tried several times but didn't work, please help me to solve it
I'm not familiar with D3.js at all but I need to visualize a graph for my project, and I need this additional feature (display label) on the nodes
Here is the input file:
{
"nodes":[
{"name":"A","group":0},
{"name":"B","group":1},
{"name":"C","group":2},
{"name":"D","group":3},
{"name":"E","group":4}
],
"links":[
{"source":0,"target":1,"value":11},
{"source":0,"target":2,"value":11},
{"source":1,"target":2,"value":21},
{"source":1,"target":3,"value":21},
{"source":1,"target":4,"value":21},
{"source":2,"target":3,"value":21},
{"source":2,"target":4,"value":21},
{"source":3,"target":4,"value":11}
]
}
D3.js Code:
<script type="text/javascript">
// use print_graph() method to output graph vertices
main_graph();
var width = 700,
height = 300;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(150)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(0,0)");
d3.json("input.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")
// size of nodes
.attr("r", 20)
.style("fill", function(d) {
return color(d.group);
})
.call(force.drag);
//I'm stuck here
var texts = svg.selectAll("text.label")
.data(graph.nodes)
.enter().append("text")
.attr("class", "label")
.attr("fill", "white")
.text(function(d) {
return d.name;
});
/*
// 1st trying
var text = svg.selectAll("text")
.data(graph.node)
.enter().append("text")
.attr("fill","white")
.text(function (d) {return d.name; });
*/
// another trying
//node.append("text")
//.text(function(d) { return d.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;
});
});
});
</script>
How you are adding the text is fine but in your force.on("tick", function() {}) handler you don't position the them:
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) { //<-- NEED TO POSITION THESE TOO
return d.x;
})
.attr("y", function(d) {
return d.y;
});
});
Here's your code fixed.
I still not understanding why the code bellow does not display its labels / text...
I have defined the css and set the attribute like the title when the move is over the node:
Json:
{
"nodes":[
{"name":"t1","group":1},
{"name":"t2","group":1},
{"name":"t3","group":1},
{"name":"t4","group":1},
{"name":"hate","group":2},
{"name":"good","group":2},
{"name":"aiport","group":3},
{"name":"flight","group":3}
],
"links":[
{"source":0,"target":4,"value":4},
{"source":0,"target":5,"value":4},
{"source":1,"target":4,"value":4},
{"source":2,"target":5,"value":4},
{"source":3,"target":5,"value":4},
{"source":4,"target":6,"value":4},
{"source":5,"target":6,"value":4},
{"source":5,"target":7,"value":4}
]
}
Code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
fill: #555;
stroke: #999;
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 = 1024,
height = 768;
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("line.link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.selectAll("circle.node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
node.append("title")
.text(function(d) { return d.name; });
node.append("text")
.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; });
});
});
</script>
</body>
</html>
You are adding the text element inside the circle element - try running your code and have a look at the svg with the DOM inspector. I'm not sure text is allowed there. Instead add the text elements separately - like another rendering of the nodes:
var texts = svg.selectAll("text.label")
.data(graph.nodes)
.enter().append("text")
.attr("class", "label")
.attr("fill", "black")
.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; });
texts.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
Another option would be to add both circle and text elements inside a g container element as shown below:
var container = svg.selectAll("g.node").data(graph.nodes).enter().append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
}).call(force.drag);
container.append("circle")
.attr("r", 5)
.style("fill", color);
container.append("text")
.style("text-anchor", "middle")
.text(function (d) {
return d.name;
});
Here you can play with a working jsfiddle:
http://jsfiddle.net/vfu78/16/