I am totally new to SVG and d3 library. I need to add dynamically add 5 circle to svg which all contains draggable event handler. I have written code for adding a single circle and adding draggable behaviour to the same and its working fine. Now I am trying the same thing inside for loop in order to add 5 circle. It displays all circle but when I drag a particular circle and put it some where then it stays there and again when i touch another circle old circle get vanished from the position where we placed and appears on new circle where we started next. Please have a look at below mentioned code. Any help regarding this would be appreciated.
function addCircles()
{
var box = d3.select(".box");
for(var i = 0;i<5;i++)
{
var drag = d3.behavior.drag()
.on('dragstart', function() { console.log("dragstart"); circle.style('fill', 'red'); })
.on('drag', function() { console.log("drag X - " + d3.event.x + " Y - " + d3.event.y); circle.attr('cx', d3.event.x)
.attr('cy', d3.event.y); })
.on('dragend', function() { console.log("dragend - " + d3.event.x);
circle.style('fill', 'green'); });
var circle = box.selectAll('.draggableCircle'+i)
.data([{ x: i*15, y: i*15, r: 10 }])
.enter()
.append('svg:circle')
.attr('class', 'draggableCircle'+i)
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', function(d) { return d.r; })
.call(drag)
.style('fill', 'green');
}
}
I have checked out after debugging code in chrome and found out that position of dragEnd is not being detected.
Your fiddle didnt work so I had to make my own from the code provided : http://jsfiddle.net/Qh9X5/6932/
Create the data first then draw circles from that data, rather than doing it all at once.
var nodeData = [];
for(var i = 1;i<15;i++) //change the value 15 to however many circles you want
{
nodeData.push({
x:i*15,
y:i*15,
r:10
})
}
Then use this data to create circles :
var circle = box.selectAll('.draggableCircle'+i)
//.data([{ x: i*15, y: i*15, r: 10 }])
.data(nodeData)
.enter()
.append('svg:circle')
.attr('class', function(d,i){
return 'draggableCircle'+i; //changed this to use i in the loop
//through the nodes not i in the for loop
})
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', function(d) { return d.r; })
.style('fill', 'green')
.call(drag)
Also your drag wasn't done correctly. You had this line :
circle.attr('cx', d3.event.x).attr('cy', d3.event.y);
You dont want this as your going through each circle and calling the drag on all of them. You only want to call it on the element you're 'dragging' like so :
function dragmove(d, i) //-updates the co-ordinates
{
d.x += d3.event.dx;
d.y += d3.event.dy;
d3.select(this).attr("transform", function(d,i)
{
return "translate(" + [ d.x,d.y ] + ")";
});
}
I think that was all the changes I made to make it work.
On a side note, with JSFiddle, you have to include the D3 library on the left hand side, other wise it wont work. Also, when calling your 'drawCircles()' function in the html, you have to change the loading of the fiddle otherwise it wont be able to find the function. Also, with all this said, if you were to use JSFiddle again in a question, please make sure it works before sending SO users a link.
EDIT
I added this line to get the correct circle positions on load :
circle.attr("transform", function(d){
return "translate(" + [ d.x,d.y ] + ")";
})
Now the drag works perfectly :)) Hope this helps
Related
I am following this example:
https://bl.ocks.org/mbostock/6123708
the thing I can't understand is how I can possibly add new circles when a button is clicked, for example:
d3.tsv("dots.tsv", dottype, function (error, dots) {
container.append("g")
.attr("class", "dot")
.selectAll("circle")
.data(dots)
.enter().append("circle")
.attr("r", 5)
.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
})
.call(drag);
});
function dottype(d) {
d.x = +d.x;
d.y = +d.y;
return d;
}
self.addNode = function () {
container.append('g')
.attr('class', 'dot')
.append('circle')
.attr('r', 35)
.attr('cx', (i * 100) + cx)
.attr('cy', (i * 100) + cy)
//.style('fill', 'purple')
.call(drag);
i++;
};
The first part is the same as the example, I then created a function to add a single circle inside the container, the problem is that when I drag the new added circle I can move only the external G element, thus moving every other circle together.
I can't understand why, as the functions are the same (I removed even the style 'fill' to be sure)
You are giving your layout a data in .data(dots) but when you are adding a node in your addNode function, the layout is unaware of this new data. What you want is to add/push the new node data to your data array(dots) and recall the drawing function.
Therefore, you should cut the code under d3.tsv into a function to call it again when you update the data.
Trying to drag groups. Why doesn't origin work here? Notice how it jumps when you first click on it? JSFIDDLE Based on this: http://bl.ocks.org/mbostock/1557377
var drag = d3.behavior.drag() // construct drag behavior
.origin(function() {
var t = d3.select(this);
return {x: t.attr("x"), y: t.attr("y")};
})
.on("drag", function(d,i) {
d.x += d3.event.dx
d.y += d3.event.dy
d3.select(this).attr("transform", function(d,i){
return "translate(" + [ d.x,d.y ] + ")"
})
});
You're mixing different ways of setting positions -- you're setting transform and cx and cyon the circles, but not on thegelements that you want to drag. While it can be made to work by computing the various offsets, it's much easier if you set the position for the things you're interested in (i.e. theg` elements) and that the drag behaviour is called on.
var svgG = svg.append("g")
.attr("transform", function(d) { return "translate(" + [ d.x,d.y ] + ")"; })
.call(drag);
Complete example here.
I'm trying to apply the fisheye effect to a force layout with collision detection, node dragging, zoom etc but am struggling to integrate a fisheye with my tick function.
The closest existing example I found was http://bl.ocks.org/bentwonk/2514276 but it doesn't really contain a custom tick function or enable the usual things like dragging a node while fisheye is applied. For the sake of clarity, here's my tick function regardless of fisheye...
function tick(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.each(gravity(.2 * e.alpha))
.each(collide(jitter))
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
And here's what I'm hoping to use on my svg listener to trigger fisheye...
svg.on("mousemove", function() {
fisheye.focus(d3.mouse(this));
node
.each(function(d) { d.fisheye = fisheye(d); })
.attr("cx", function(d) { return d.fisheye.x; })
.attr("cy", function(d) { return d.fisheye.y; })
.attr("r", function(d) { return d.fisheye.z * 8; });
link.attr("x1", function(d) { return d.source.fisheye.x; })
.attr("y1", function(d) { return d.source.fisheye.y; })
.attr("x2", function(d) { return d.target.fisheye.x; })
.attr("y2", function(d) { return d.target.fisheye.y; });
});
But when using both together I notice no fisheye effect - presumably tick() is overriding any changes from my svg listener (although even if I add force.stop() to my svg listener I still see no fisheye on the page or any console errors).
And of course if I replace the tick function, then the whole node layout won't compute properly to start with.
Apologies that I don't have a more specific question, but does anyone have any thoughts on the best way to approach combining these 2 behaviours so that I can use a fisheye effect without compromising on other force layout functionality?
Thanks!
EDIT:
My only thought so far is to simply use the svg listener to trigger the mouse position...
svg.on("mousemove", function() {
//force.stop();
fisheye.focus(d3.mouse(this));
});
And then have some kind of coordinate addition in the tick function...
node.each(gravity(.2 * e.alpha))
.each(collide(jitter))
.each(function(d) { d.fisheye = fisheye(d); })
.attr("r", function(d) { return d.fisheye.z * 8; })
.attr("transform", function(d) {
return "translate(" + d.x + d.fisheye.x + "," + d.y + d.fisheye.y + ")";
});
(the above doesn't work though - just produces invalid coordinates so all nodes end up at the top-left of the screen)
I am trying to implement an extended version of the Grouped Bar Chart. Everything works fine, except updating the plot. In a regular bar chart, I would do something like this:
function draw(data) {
// Join the data with the selection
var bars = svg.selectAll(".bar")
.data(data);
// Create new bars if needed
bars.enter().append("rect")
...
// Update existing bars if needed
bars.transition()
...
// Remove bars if needed
bars.exit().remove();
}
Works like a charm. Now i tried the same with my groups:
var groups = chart.selectAll(".group")
.data(data);
groups.enter().append("g")
.attr("class", "group")
.attr("transform", function (d) {
return "translate(" + 0 + "," + y(d.name) + ")";
});
var bars = groups.selectAll(".bar").data(function (d) {
return d.values;
});
bars.enter().append("rect")
...
groups.transition().duration(750).attr("transform", function (d) {
return "translate(" + 0 + "," + y(d.name) + ")";
});
groups.exit().remove();
But: it only appends more and more groups on every update, no transitions or exits.
I am aware of this question: D3: update data with multiple elements in a group. However, I think did the setup just as described in the answer, without success.
EDIT: I am still trying to figure this out. I updated the fiddles.
Here is the working example without groups: http://jsfiddle.net/w2q0kjgd/2/
This is the not working example with groups: http://jsfiddle.net/o3fpaz2d/9/
I know it has been almost a month since the original posting, I tried to do something else in between and then have a look at this problem again, sometimes this helps. But I just cannot figure it out...
Please update the group before you select all the bars..
var groups = chart.selectAll(".group")
.data(data);
groups.enter()...
groups.transition().duration(750).attr("transform", function (d) {
return "translate(" + 0 + "," + y(d.name) + ")";
});
var bars = groups.selectAll(".bar").data(function (d) {
return d.values;
});
//add bars
//update bars
bars.exit().remove();
groups.exit().remove();
I was also playing around with the same block and was trying to figure it out. In the end, this solution worked for me:
var bars = g
.selectAll("g")
.data(data, function(d) {return d ? d.format_date : this.id; });
var enter = bars
.enter().append("g");
bars = enter.merge(bars)
.attr("transform", function(d) { return "translate(" + x0(d.format_date) + ",0)"; });
var rect = bars
.selectAll("rect")
.data(function(d) { return keys.map(function(key) { return {key: key, value: d[key]}; }); })
rect
.enter().append("rect").merge(rect)
.attr("x", function(d) { return x1(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("width", x1.bandwidth())
.attr("height", function(d) { return height - y(d.value); })
.attr("fill", function(d) { return z(d.key); });
I think the trick here is that you need to update and merge it twice. One is for your x0 axes overall group and the other is for the bars within that group. Hope this also works out for you.
I'm having a bit of trouble getting a something to work with D3.js. Namely, I'm trying to make a tree of nodes, using the basic code from http://bl.ocks.org/mbostock/1021953.
I switched it to load the data inline, as opposed to loading from file, because I'm using it with a Rails application and don't want to have repetitive information. I switched the line so that you could see the format of my data.
Anyways, here's the bulk of my code:
<%= javascript_tag do %>
var nodes = [{"title":"Duncan's Death","id":"265"},{"title":"Nature Thrown Off","id":"266"},{"title":"Cows Dead","id":"267"},{"title":"Weather Bad","id":"268"},{"title":"Lighting kills man","id":"269"},{"title":"Macbeth's Rise","id":"270"}];
var links = [{"source":"265","target":"266","weight":"1"},{"source":"266","target":"267","weight":"1"},{"source":"266","target":"268","weight":"1"},{"source":"268","target":"269","weight":"1"}];
var firstelement = +links[0].source;
links.forEach(function(l) {
l.source = +l.source;
l.source = l.source-firstelement;
l.target = +l.target
l.target = l.target-firstelement;
});
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-1000)
.linkDistance(300)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
force
.nodes(nodes)
.links(links)
.start();
var link = svg.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.weight); });
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("class", "circle_node")
.attr("r", 50)
.style("fill", function(d) { return color(d.id); })
node.append("title")
.text(function(d) { return d.title; });
node.append("text")
.attr("x", function(d) { return d.x; } )
.attr("y", function(d) { return d.y; })
.text(function(d) { return d.title; });
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(a) { return a.x; })
.attr("y", function(a) { return a.y; });
});
<% end %>
This seems like it should work to me, but I can seem to manage it. The links work, but the nodes all remain in the top left corner. I've tried just entering the circles directly and appending the text to them (staying close to the source code I listed above,) but while the circles behave properly, it doesn't display the text. I'd like the title to be centered in the nodes.
More generally, I'm kind of confused by how this is working. What does "d" refer to within lines like
function(d) { return d.source.x; }
It seems to be declaring a function and calling it simultaneously. I know that it doesn't have to be specifically the character "d," (for instance, switching the "d" to an "a" seems to make no difference as long as it's done both in the declaration and within the function.) But what is it referring to? The data entered into the object that's being modified? For instance, if I wanted to print that out, (outside of the attribute,) how would I do it?
Sorry, I'm new to D3 (and fairly new to JavaScript in general,) so I have a feeling the answer is obvious, but I've been looking it up and through tutorials and I'm still lost. Thanks in advance.
First, there's a simple problem with your code that is causing all your nodes to stay in the top left corner. You are trying to position each node using the code:
node.attr("x", function(a) { return a.x; })
.attr("y", function(a) { return a.y; });
However, node is a selection of gs which do not take x and y attributes. Instead, you can move each node using translate transform, e.g.
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
Making this change should allow the nodes to move around.
Next, moving to your question about "d", I think the first thing you need to understand is what you can do with a selection of elements in D3. From the docs: a selection (such as nodes) "is an array of elements pulled from the current document." Once you have a selection of elements, you can apply operators to change the attributes or style of the elements. You can also bind data to each element.
In your case, you are binding data to a selection of gs (nodes):
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
You are then using attr to change the position of each node. However, instead of setting the x and y attributes of each element to the same value, you are passing attr an anonymous function that will return a (presumably different) position for each node:
node.attr("x", function(a) { return a.x; })
.attr("y", function(a) { return a.y; });
This behavior is also explained in the docs for attr:
Attribute values and such are specified as either constants or
functions; the latter are evaluated for each element.
Thus, d represents an individual element (Object) in nodes.
So going back to your code, on each tick two things are happening:
The position of each node (data) is being recalculated by force.
Each corresponding element is then being moved to its new location by the anonymous function you pass to force.on.