I would like to create a static force-directed layout as the one in this example: http://bl.ocks.org/mbostock/1667139, but with the first and last node anchored on each side of the window (contrary to the example, my graph is not circular). This way I could clearly see where the graph starts and ends and also hopefully be able to give it a more linear conformation.
So far I have attempted to assign different classes to these two nodes and then give those classes fixed coordinates, like:
svg.select(".first")
.attr("cx", function(d) { return 10; })
.attr("cy", function(d) { return height/2; })
svg.select(".last")
.attr("cx", function(d) { return width-10; })
.attr("cy", function(d) { return height/2; })
But it doesn't seem to be working, the two nodes are just sitting in the upper right corner of the window. Any ideas?
Placing the following code between the for loop and force.stop() seemed to work fine for me.
var nodes = force.nodes();
nodes[0].x = 10;
nodes[0].y = height/2;
nodes[nodes.length-1].x = width-10;
nodes[nodes.length-1].y = height/2;
Here's a Fiddle showing this. You might also want to look into the fixed property, which could help with the behaviour you're looking for.
Related
I have a hard time positioning text label on my force chart. They are overlapping each other, and I could not figure out how to fix it. And I've tried many solutions from online, none of them works well. Could you please help me take a look?
Here are the code for my text labels:
var node_text = node_textNew.append("svg:text")
.attr("class", "text_note")
.attr("dx", 0)
.attr("dy", -0.5)
.attr('background-color', '#fff')
.attr("x", function(d, i) { return circleWidth + 5; })
.attr("y", function(d, i) { if (i>0) { return circleWidth + 0 } else { return 8 } })
.text(function(d) { return d.name});
Here is how it looks right now:
Thank you so much for your help!
Try this one http://bl.ocks.org/MoritzStefaner/1377729.
Here the author introduces a way when a label is placed near a node using another force layout.
One easy solution I have found us to use the center of a nodes Voronoi cell as the anchor for a label. This gives you the optimal spacing provided by your graph.
An example of this is seen in:
https://bl.ocks.org/mbostock/6909318
I am a beginner on D3.js and hit the wall on the following:
I wish to display the score given by an attendee of an event over time. Then as the attendee can give also comments, I would like to place a circle on the curve of the score in the same colour as the scoring.
I succeeded to do this for a single user.
The code is on JS Fiddle http://jsfiddle.net/roestigraben/8s1t8hb3/
Then, trying to extend this to multiple attendees, I run into problems.
The JSFiddle http://jsfiddle.net/roestigraben/Lk2kf1gh/
This code displays nicely the score data for the 3 attendees simulated. However the circles to display the possible comments (there is only one in the data set) from the attendees do not work
I try to filter the attendees array
svg.selectAll("circle")
.data(data.filter(function(d, i){ if(d.comment){return d}; })) // condition here
.enter().append("circle")
.attr("class", "dotLarge")
.attr({r: 5})
.attr("cx", function(d) { return x(d.time); })
.attr("cy", function(d) { return y(d.status); })
I think I need to go deeper into the nesting, but ....my ignorance.
Thanks a lot
Peter
The code where you're displaying your circles doesn't even come close to matching the format of your data. I don't know that you're having a problem with the nest, but you probably want a slightly different data structure when it comes to graphing your comments.
I have updated your fiddle here: http://jsfiddle.net/Lk2kf1gh/7/
The important bit is:
var comments = attendees.map(function(d) {
return {
name: d.name,
comments: d.values.filter(function(e) {
return e.comment != undefined;
})
};
});
//generation of the circles to indicate a comment
// needs to be done with a filter
svg.selectAll("g.comments")
.data(comments)
.enter()
.append("g")
.attr("class", function(d) { return "comments " + d.name })
.selectAll("circle.comment")
.data(function(d) {
return d.comments;
})
.enter()
.append("circle")
.attr("class", "dotLarge comment")
.attr("r", 5)
.attr("cx", function(e) { return x(e.time); })
.attr("cy", function(e) { return y(e.status); });
The first part of this code creates a new data structure that is more focused on the comment information. The second part creates the circles.
Note: I've grouped each person into their own g element and then created a circle for each of their comments. This makes use of d3 nested selections and data binding. Hopefully you can follow what is happening.
I've also added a few more comments to your data for testing. I didn't bother to fix any of the cosmetic issues, I'll leave that up to you.
I thought I understood the D3 enter/update/exit, but I'm having issues implementing an example. The code in http://jsfiddle.net/eamonnmag/47TtN/ illustrates what I'm doing. As you'll see, at each 5 second interval, I increase the rating of an item, and update the display again. This works, in some way. The issue is in only updating what has changed - D3 is updating everything in this case. The enter and exit methods, displayed in the console output that nothing has changed, which makes my think that it's treating each array as a completely new instance.
My understanding of the selectAll() and data() calls was that it would 'bind' all data to a map called 'chocolates' somewhere behind the scenes, then do some logic to detect what was different.
var chocolate = svg.selectAll("chocolates").data(data);
In this case, that is not what's happening. This is the update code. Any pointers to what I've missed are most appreciated!
function update(data){
var chocolate = svg.selectAll("chocolates").data(data);
var chocolateEnter = chocolate.enter().append("g").attr("class", "node");
chocolateEnter.append("circle")
.attr("r", 5)
.attr("class","dot")
.attr("cx", function(d) {return x(d.price)})
.attr("cy", function(d) {
//put the item off screen, to the bottom. The data item will slide up.
return height+100;})
.style("fill", function(d){ return colors(d.manufacturer); });
chocolateEnter
.append("text")
.text(function(d) {
return d.name;})
.attr("x", function(d) {return x(d.price) -10})
.attr("y", function(d){return y(d.rating+step)-10});
chocolateEnter.on("mouseover", function(d) {
d3.select(this).style("opacity", 1);
}).on("mouseout", function(d) {
d3.select(this).style("opacity", .7);
})
chocolate.selectAll('circle')
.transition().duration(500)
.attr('cy', function(d) {return y(d.rating+step)});
var chocolateExit = chocolate.exit().remove();
chocolateExit.selectAll('circle')
.attr('r', 0);
}
setInterval(function() {
chocolates[3].rating = Math.min(chocolates[3].rating+1, 5);
update(chocolates);
}, 5000);
Easy as apple pie!
Why are you doing svg.selectAll("chocolates")? There is no HTML element in your DOM called chocolates.
You need to change that to svg.selectAll(".node"). That will fix the problem.
There are a couple of issues in your code. First, the logic to detect what's different is, by default, to use the index of the item. That is, the first data item is matched to the first DOM element, and so on. This works in your case, but will break if you ever pass in partial data. I would suggest using the second argument to .data() to tell it how to match:
var chocolate = svg.selectAll("g.node").data(data, function(d) { return d.name; });
Second, as the other poster has pointed out, selecting "chocolate" will select nothing, as there are no such DOM elements. Just select the actual elements instead.
Finally, since you're adding g elements for the data items, you might as well use them. What I mean is that currently, you're treating the circles and text separately and have to update both of them. You can however just put everything underneath g elements. Then you have to update only those, which simplifies your code.
I've made all the above changes in your modified fiddle here.
This is a hard one to explain but hopefully its an easy one to answer...bear with me...i considered js fiddling it but I would have to rewrite a lot of code as the data it uses is from my internal server.
I have written a long amount of js to model a network map and its too long, and the essence of the issue is that I can draw the map with initial data fine but it corrupts sometimes when I update it - namely when I remove nodes from the original map (though I can add new nodes fine)
I believe the issues are due to the way I have drawn the svg objects.
I am trying to work d3 out on the fly a bit so I may have deviated unintentionally from what is right.
First off I define vis (window.vis as it is now so I can test it with the console) - which I think some examples use the variable svg for.
window.vis = d3.select("body")
.append("svg:svg")
.attr("pointer-events", "all")
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", redraw))
.append('svg:g');
then I do some other stuff, creating nodeArray (an array of the nodes I want) and linkArray( an array of links I want) then I draw the nodes and links:
link = vis.selectAll("line").data(linkArray);
link.enter().append("line")
.attr("stroke-opacity", function (d, i) {
if (d.class == 'link reallink') {
return '0.8';
} else {
return '0';
};
})
.attr("stroke-width", function (d, i) {
if (d.class == 'link reallink') {
return '3';
} else {
return '0';
};
})
.style("stroke", function (d, i) { return d.color; });
node = vis.selectAll("g.node").data(nodeArray);
node.enter().append("svg:g")
.attr("class", function (d) { return d.class })
.attr("id", function (d) { return d.id })
.call(force.drag);
//append to each node an svg circle element
vis.selectAll(".realnode").append("svg:circle")
.attr("r", function (d, i) {
if (d.status != "0") {
return r*2;
} else {
if (d.iconimage == "") { return r; } else { return 1; }
}
})
.style("fill", function (d, i) { if (d.status != "0") { if (d.status == "1") { return "#ff0000"; } else { return "#FFBF00"; } } else { return "#FFFFFF"; }})
.style("stroke", function (d) {
if (d.style !== 'filled') { return d.color; };
})
.style("stroke-width", "4");
//attach images to the nodes
vis.selectAll(".realnode").append("image")
.attr("xlink:href", function (d) { return d.iconimage; })
.attr("x", -16)
.attr("y", -16)
.attr("width", 32)
.attr("height", 32);
NB: node classes I use are node (all nodes) and realnode (real objects) (as I also model the text labels later on as a different class labelnode). nodeArray is just an array of objects formatted as nodes and linkArray is just an array of links.
You can see that I define 'link' and 'node' - pretty much as all the examples do - but I found circles and images did not append properly if I used node.selectAll(".realnode").append... but works fine if I use vis.selectAll(".realnode").append... so I just did that and moved on
However I think I need to solve this lack of understanding!
Later on when I delete nodes out of nodeArray and links out of linkArray, and update the display I am again utilising the 'node' and 'link' objects in a haphazard mix with 'vis' - and at that point it all goes wrong and corrupts. Although the svg element on the page still has the right objects defined, images swap, text swaps and the links float off on their own without the nodes! I have checked nodeArray and linkArray and they are completely correct, and if I use the "new" data after the update as the original data when I first load the page it renders fine so I am fairly confident of my data objects.
I think the best way is for me to answer any questions and update this as I go along, as I hope someone will just look at this and see what I am doing wrong. The really annoying thing is the initial page load always works perfectly but the updating of data is taking me longer than the whole initial page draw code from scratch!
Thanks
--Chris
If I understand the code and the description right, the nodeArray has some items with class node and others with class realnode. When you create the nodes, you are selecting only the nodes of class node, this allows the creation of groups of each class, but bounds the selection only to the groups of class node. I think that you need to create the nodes with both classes
nodes = vis.selectAll('g')
.data(nodeArray)
...
.call(force.drag);
And then operate over selections based in the class of the elements:
nodes.selectAll('g.realnode')
// ... set attributes here
Also, I would suggest using css for setting the attributes of link and node classes:
links = vis.selectAll('line')
.classed('real-link', function(d) { return d.class == 'link reallink'; });
and in the css:
svg .real-link {
stroke-opacity: 0.8;
stroke-width: 3;
}
I have a network of nodes and links. One of them has a fixed position in the center but is draggable, the others are in a force field around the centered one. If the user drags any node, the others will be draged behind him, because the are linked. Is there a possibility to drag the others with the centered node, but keeping the drag-event of the other nodes single?
thanks for thinking about it,
David
edit: if someone knew a possibility to set a dragg-listener for all the other nodes to the centered one, the problem would be solved. I'd be grateful if you had an idea!
Please leave me a comment which parts of th ecode could help you solve this issue, and I'll post it asap!
edit: with the help of nrabinowitz I can now move the nodes just as I wanted! But the new code-parts somehow crashed my coordinate-restrictions. For the nodes not to drop out of the svg, I put a cx/cy-attr to all nodes, preventing them from crossing the border of svg. This still works in the beginning, but after the first drag of the center-node (and therefore the 'g'-element) the restrictions seem to shift. Is there anything dragged except the svg?
The part of the script providing the restriction is
force.on("tick", function() {
node.attr("cx", function(d) { return d.x = Math.max(15, Math.min(width - 15, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(15, Math.min(height - 15, d.y)); });
node.attr("transform", function(d) { 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; });
});
See working fiddle: http://jsfiddle.net/nrabinowitz/4Rj4z/
This encloses the nodes in a g element, and uses the transform attribute to move them around.
In order to get this to work, you can't use force.drag for the node you want to have pull the group - you need a custom d3.behavior.drag. Unfortunately, per this answer, you can't put the drag handler on the node itself - it needs to be on the group. This works, but it means that other elements without a separate drag handler - e.g. links - will also drag the group. You might be able to fix this with pointer-events on the group.