Iam using d3.js to create a forced graph.
Array nodes and links are filled dynamically. After I'am calling function start()
function start() {
link = link.data(force.links(), function (d) {
return d.source.id + "-" + d.target.id;
});
link.enter().insert("line", ".node").attr("class", "link");
link.exit().remove();
node = node.data(force.nodes(), function (d) { return d.id; });
var nodeEnter = node.enter()
.append("circle")
.attr("r", 8);
nodeEnter.append("text")
.attr("dx", -40)
.attr("dy", 55)
.text(function (d) { return d.id });
node.exit().remove();
force.start();
}
Nodes are OK, links are OK, but the text is not visible!
How can I correctly append the text node in this case?
You're appending a text node to a circle node, which is not valid SVG. Instead you should do something like:
var nodeEnter = node.enter()
.append('g')
.style("transform", "translate(...)")// Here you'll probably want to translate the group)
And then nest both circle and text inside the group:
nodeEnter
.append('circle')
.attr("r", 8);
nodeEnter.append("text")
.attr("dx", -40)
.attr("dy", 55)
.text(function (d) { return d.id });
Thank you for your answer. I tested group element
node = node.data(force.nodes(), function (d) { return d.id; });
var NodeEnter = node.enter()
.append("g");
NodeEnter.append("circle")
.attr("r", 8);
Unfortunately, I see only one circle in the corner of the screen.
Generated svg for 4 nodes is
<svg height="500" width="960">
<line>
...
y1="326.81906005822043" x1="454.86800328354803"
... </line>
<g cy="326.81906005822043" cx="454.86800328354803">
<circle r="8"></circle>
</g>
...
</svg></div>
Also "g" element is in OK position on link(node). Cirle is inside element. Yet I see element in corner in screen
Related
Here is a demo
When new data hits my d3 service it loads the new data set but the old isn't removed. Therefore I have duplicate nodes inside its parent node 'g' element. New to d3, however I've done lots of reading around selection.join() instead of enter().append(). I've also read up on ways to add node.exit().remove(); and node.merge(node); at specific points.
As you can see from the dom, all new node properties are in the <g class="node"> element, duplicated, not replacing the original data. Therefore I get a overlapping of content.
Here is the way my nodes are built...
const zoomContainer = d3.select('svg g');
const node = zoomContainer.selectAll('g').data(nodes, function (d) {
return d.id;
});
//zoomContainer.selectAll('.node').data(node).exit().remove();
const nodeEnter = node
.join('g')
.attr('class', 'node')
.call(
d3
.drag()
.on('start', (d) => this.dragended(d3, d, simulation))
.on('drag', function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
})
.on('end', (d) => this.dragended(d3, d, simulation))
);
nodeEnter
.append('circle')
.style('fill', '#fff')
.style('cursor', 'pointer')
.style('fill-opacity', '1')
.style('stroke-opacity', '0.5')
.attr('id', (d, i) => d.id)
.attr('r', 28);
nodeEnter
.append('image')
.attr('xlink:href', 'https://github.com/favicon.ico')
.attr('x', -15)
.attr('y', -60)
.attr('width', 16)
.attr('class', 'image')
.style('cursor', 'pointer')
.attr('height', 16);
const nodeText = nodeEnter
.data(nodes)
.append('text')
.style('text-anchor', 'middle')
.style('cursor', 'pointer')
.attr('dy', -3)
.attr('y', -25)
.attr('class', 'nodeText')
.attr('id', 'nodeText');
nodeText
.selectAll('tspan')
.data((d, i) => d.label)
.join('tspan')
.attr('class', 'nodeTextTspan')
.text((d) => d)
.style('font-size', '12px')
.attr('x', -10)
.attr('dx', 10)
.attr('dy', 15);
I probably could clear the graph by force but I like and need the way .join() can compare what's changed and the options to use enter().append().exit(). If anybody can see why duplicates are not being removed/merged I would appreciate it.
UPDATE:
If I use enter().append('g') instead of join('g') I then get a better result. I can use zoomContainer.selectAll('.node').data(node).exit().remove(); before hand and my nodes do get updated but only after clicking update twice. If I use join('g') they duplicate and I am unable to use zoomContainer.selectAll('.node').data(node).exit().remove();
Here is a demo
Targeting the <g> element rather than the class and then using exit().remove() seemed to have done the trick... I was adding a class attribute at the .enter() level in .join() and then doing the exit on that. Demo here
const node = zoomContainer
.selectAll('.node')
.data(this.nodes, function (d) {
return d.id;
});
zoomContainer.selectAll('g').data(node).exit().remove();
The following code displays a graph based on http://my-neo4j-movies-app.herokuapp.com/
Now I want to make the individual nodes react to click events, similar to http://bl.ocks.org/d3noob/5141528, and increase thier size with help of the click() function, but the attribute doesn’t change. If I remove the two lines
.append("circle")
.attr("r", 5)
in the variable definition of 'node' the code breaks.
<script type="text/javascript">
var width = screen.width, height = screen.height;
var force = d3.layout.force().charge(-200).linkDistance(30)
.size([width, height]);
var svg = d3.select("#graph").append("svg")
.attr("width", "100%").attr("height", "100%")
.attr("pointer-events", "all");
d3.json("/graph", function(error, graph) {
if (error) return;
force.nodes(graph.nodes).links(graph.links).start();
var link = svg.selectAll(".link")
.data(graph.links).enter()
.append("line").attr("class", "link");
var node = svg.selectAll(".node")
.data(graph.nodes).enter()
.append("circle")
.attr("r", 5)
.attr("class", function (d) { return "node "+d.label })
.on("click", click)
.call(force.drag);
// add the nodes
node.append("circle").attr("r", 5);
// html title attribute
node.append("title").text(function (d) { return d.title; });
// force feed algo ticks
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; });
});
// action to take on mouse click
function click() {
console.log(d3.select(this));
d3.select(this).select(".circle")
.attr("r", 26)
.style("fill", "lightsteelblue");
}
});
In your second example the "node" is a g element with circle and text like this:
<g>
<circle />
<text>some text</text>
</g>
So this code in the click:
d3.select(this).select(".circle")
is saying select the g and grab the first thing with class circle (the circle).
Your code though, the node is just a circle. So, in the click, just do this:
d3.select(this)
.attr("r", 26)
.style("fill", "lightsteelblue");
I am trying to add text in force layout. First i am creating a svg group and i am appending circle and text into it. The circle is working fine but the text is not working. Here is the code
var node = svg.selectAll("g")
.data(measures.nodes)
.enter().append("g")
.attr("class", "node")
.call(node_drag);
var circle = node.append("circle")
.attr("fill", "blue")
.attr("r",5)
.attr("dx", ".10em")
.attr("dy", ".10em");
var text = node.append("text")
.data(measures.nodes)
.attr("color", "blue")
.text(function(d){ return d.name; })
The text is of the screen because you've missed out the positioning methods. If you add this you'll see text attached to the nodes.
text.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
OBJECTIVE: append text to each node in a d3 force layout
BUG: text is appended to the object (I think, see console) but not displayed on screen
Here's the jsfiddle.
node.append("svg:text")
.text(function (d) { return d.name; }) // note that this works for
// storing the name as the id, as seen by selecting that element by
// it's ID in the CSS (see red-stroked node)
.style("fill", "#555")
.style("font-family", "Arial")
.style("font-size", 12);
I'd be so grateful for any thoughts.
You can't add svg text to a svg circle. You should first create an svg g object (g stands for group) for each node, and than add a circle and a text for each g element, like in this code:
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g");
var circle = node.append("circle")
.attr("class", "node")
.attr("id", function (d) { return d.name; })
.attr("r", 5)
.style("fill", function (d) {
return color(d.group);
});
var label = node.append("svg:text")
.text(function (d) { return d.name; })
.style("text-anchor", "middle")
.style("fill", "#555")
.style("font-family", "Arial")
.style("font-size", 12);
Of course, tick function should be updated accordingly: (also css a little bit)
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 - 10;
});
Here is jsfiddle.
I am new to D3JS, I need to create force layout with both image or circle for each node.
that means, A Image node or Circle node be added dynamically.
Is this possible?, if any examples please answer
Well if you just want to have an image in the middle of the circle try this:
/* Create nodes */
var node = vis.selectAll("g.node")
.data(json.nodes) // get the data how you want
.enter().append("svg:g")
.call(node_drag);
/* append circle to node */
node.append("svg:circle")
.attr("cursor","pointer")
.style("fill","#c6dbef")
.attr("r", "10px"})
/* append image to node */
node.append("image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", -8)
.attr("y", -8)
.attr("width", 16)
.attr("height", 16);
You can also append a title, some text... See the documentation of selection.append(name) for more info.
For Dynamic Image, you can keep the image in local and using image name you can use the dynamic image from the local.
For making circle, you can use :
var groups = node.enter().append("g")
.attr("class", "node")
.attr("id", function (d) {
return d.entityType;
})
.on('click', click)
groups.append("circle")
.attr("cursor", "pointer")
.style("fill", function(d) { return color(d.entityType); })
.style("fill", "#fff")
.style("stroke-width", "0")
.style("stroke", "#ddd")
.attr("r", 20);
Here, d.entityType will be the name of the image example : favicon.png
groups.append("image")
.attr("xlink:href",function (d) {
return "../assets/images/"+d.entityType+".png";
})
.attr("x", -14)
.attr("y", -15)
.attr("width", 30)
.attr("height", 30)
If you want to add text inside the circle, you can use :
groups.append("text")
.attr("dy", 18)
.style("font-size", "2.5px")
.style("text-anchor", "middle")
.style('fill','#000')
.attr("refX", 15)
.attr("refY", -1.5)
.text(function (d) {
return d.entityName;
});