D3 drag event behaviour: DragEvent.x and DragEvent.y values source - javascript

I don't understand the behaviour of the D3 DragEvent object in Mike Bostock’s examples for D3.
Those are the two examples I don't understand:
Circle Dragging I
Drag + Zoom
This is the code that I don't understand:
function dragged(d) { d3.select(this).attr("cx", d.x =
d3.event.x).attr("cy", d.y = d3.event.y); }
In this example, d.x and d.y are part of a data object that contains the last centre of the circle.
What I expect to happen with this code, is that the attributes cx and cy of the circle change to the current value of d3.event.x and d3.event.y
In my view, that should be the coordinates where the mouse is when the user start dragging. If the user is not starting to drag exactly in the centre of the circle, the behaviour I expect is the circle to jump to the place where the user start dragging and, then, a normal dragging behaviour.
My problem is that the initial jump doesn't happen and I don't know why.
I checked the values of d3.event.x and d3.event.y when the code is running and they are exactly the same values of d.x and d.y, never mind where the user click in the circle. I don't understand why that is the case.
Finally, I changed the name (and only the name) in the object (and whenever was necessary in the code), from d.x and d.y to d.m and d.n and the code started to do what I was expecting.
function dragged(d) { d3.select(this).attr("cx", d.m =
d3.event.x).attr("cy", d.n = d3.event.y); }
So, the code works differently when the data object has the properties d.x and d.y that when the data object has the properties d.m and d.n.
It seems like the object DragEvent is expecting a data object with concrete proprieties but that sounds weird to me and I can't find it documented.
I was expecting to avoid checking D3 source code if somebody know the answer already.

What I though a weird behaviour is the intended behaviour.
All DragEvent objects have a 'subject'. The subject is not the SVG shape that is producing the dragging event, but, if it exist, the datum associated to that SVG.
More information available in: https://github.com/d3/d3-drag#drag_subject
So, yes, if the datum object have the the properties x and y, the value would be assigned to the properties dx and dy of the DragEvent object.
As we can see in the d3 source, in the drag.js file:
dx = s.x - p[0] || 0;
dy = s.y - p[1] || 0;

Related

Strange path/line behaviour in d3 radial graph

I'm trying to modify d3 radial tree graph example I found out and abouton the internet. Original had a diagonal, curved paths between nodes and I needed it to be straight lines.
After hours of struggle I finaly managed to do just that. Well, kinda.
As you can see in this pen, the lines connecting nodes are indeed straight but also they are not originating from the same, central point. For the central node and first level of links, line starting points are converging on the edges of said node, and higher level nodes are grouping lines in sort of rows instead of one, central point I crave and desire so much.
The part of code responsible for drawing the links in the pen starts with line 252and ends with 276, looking like this:
var link = svg.selectAll("link")
.data(links)
.enter().append("path")
.attr("class", "link");
var lines = svg.selectAll('line')
.data(links)
.enter()
.append('line')
.attr('stroke',function(d) {
if(d.source.depth === 0) {
return 'red'
} else {
return '#ccc';
}
});
lines.attr('x1',function(d){return d.source.y})
.attr('y1',function(d){return d.source.x/180*Math.PI})
.attr('x2',function(d){return d.target.y })
.attr('y2',function(d){return d.target.x/180*Math.PI});
lines.attr("transform", function(d) {
return "rotate(" + (d.target.x - 90 ) + ")";
});
I've already tried everything I could come up with, as far as drawing x1, x2, y1, y2 goes, with switching them around, changing the maths in there and whatnot. Also, tried to alter rotation and forcing the coordinates onto the links. Everytime coming back empty. Could You, good people of SO, take a peek and point me in the right direction?
Not enough reputation to comment, so posting as answer. I've done that a long time ago, but don't remember it now. Your x1, y1, x2, y2 values seems wrong.
I gave that x1 = d.source.x, x2 = d.target.x, y1 = d.source.y, y2 = d.target.y. And then, rotated that line with x1, y1 as it's center to the angle(which I forgot sorry).
You should be doing rotation with translation

D3.js Dynamic connector between objects

I'm very new to both JS and D3, and I've googled this a tonne but only found examples that are a bit too advanced.
I'm making a simple decision graph implementation, and I'm stuck trying to connect 2 nodes with a line / path. The objects can be moved around with the mouse, and the path should always update to reflect the positions of the objects.
This is my base source of knowledge: https://github.com/mbostock/d3/wiki/SVG-Shapes, but I don't quite understand how to do something smart with it.
Here is what I have so far: http://jsbin.com/AXEFERo/5/edit
Don't need the fancy stuff, just need to understand how to create connectors and have them update dynamically when the objects are being dragged around. Big thanks!
To draw a line between the circles, you don't need anything special -- just the line element.
var line = svg.append("line")
.style("stroke", "black")
.attr("x1", 150)
.attr("y1", 100)
.attr("x2", 250)
.attr("y2", 300);
Updating the position dynamically is a bit more difficult. At the moment, you have no means of distinguishing which of the circles is being dragged. One way of doing this is to add a distinguishing class to the g elements.
var g1 = svg.append("g")
.attr("transform", "translate(" + 150 + "," + 100 + ")")
.attr("class", "first")
...
and similarly for the other one. Now you can switch on the class in your dragmove function and update either the start or the end coordinates of the line.
if(d3.select(this).attr("class") == "first") {
line.attr("x1", x);
line.attr("y1", y);
} else {
line.attr("x2", x);
line.attr("y2", y);
}
Complete example here. There are other, more elegant ways of achieving this. In a real application, you would have data bound to the elements and could use that to distinguish between the different circles.

How to extend the Diagonal Object in D3.js? [duplicate]

I'm very new to d3.js (and SVG in general), and I want to do something simple: a tree/dendrogram with angled connectors.
I have cannibalised the d3 example from here:http://mbostock.github.com/d3/ex/cluster.html
and I want to make it more like the protovis examples here:
http://mbostock.github.com/protovis/ex/indent.html
http://mbostock.github.com/protovis/ex/dendrogram.html
I have made a start here: http://jsbin.com/ugacud/2/edit#javascript,html and I think it's the following snippet that's wrong:
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
However there's no obvious replacement, I could use d3.svg.line, but I don't know how to integrate it properly, and ideally I'd like an elbow connector....although I am wondering if I am using the wrong library for this, as a lot of the d3 examples I've seen are using the gravitational force to do graphs of objects instead of trees.
Replace the diagonal function with a custom path generator, using SVG's "H" and "V" path commands.
function elbow(d, i) {
return "M" + d.source.y + "," + d.source.x
+ "V" + d.target.x + "H" + d.target.y;
}
Note that the source and target's coordinates (x and y) are swapped. This example displays the layout with a horizontal orientation, however the layout always uses the same coordinate system: x is the breadth of the tree, and y is the depth of the tree. So, if you want to display the tree with the leaf (bottommost) nodes on the right edge, then you need to swap x and y. That's what the diagonal's projection function does, but in the above elbow implementation I just hard-coded the behavior rather than using a configurable function.
As in:
svg.selectAll("path.link")
.data(cluster.links(nodes))
.enter().append("path")
.attr("class", "link")
.attr("d", elbow);
And a working example:
http://bl.ocks.org/d/2429963/

drag multiple nodes in d3 force directed layout

Precondition: d3 force directed layout;
some nodes are selected by sequential clicking one by one (visually they become bigger and in the code pushed to array)
Is there a way to drag them all by picking one with the mouse the same way as files in the Windows explorer?
P.S. I'm getting very much answers here on stackoverflow without asking for a long time. This is my first question. Thanks in advance for any help!
The way that I impemented the dragging of multiple nodes (based off of children) was by recording the displacement of the dragged node inside of my tick function with a variable whose scope allows the value to still exist the next time tick runs.
You will need an object where the key is a unique indentifier of the node being dragged and the value is a d3 selection of the nodes that you would like to translate/drag when the key node is dragged.
dragObject is the above-mentioned object.
nodeData is the d3 data of the principle node that you are dragging - ( d3.select(node uid).datum() ).
offset.x and offset.y make up the above-mentioned variable that was defined the last time tick was run.
var translateAllChildren = function (nodeData) {
if (dragObject[nodeData.uid]) {
dragObject[nodeData.uid]
.attr("transform", function(d) {
d.x = (d.x + offset.x);;
d.y = (d.y + offset.y);
return "translate(" + d.x + "," + d.y + ")";
});
}
}

Using Javascript D3 library, how can I determine mouse position in data set of an area element on mousemove event?

I am trying to setup a tooltip for an area path that I created. I checked all the arguments being passed into the on mousemove event handler, and I'm just getting the full data set, 0, 0. Nothing to indicate my index in the data as far as I can see. "This" context also is the svg path element. Still nothing useful. Even looked at d3.select(this), and I can't find the index anywhere there either. Is there some way to determine over which data point my mouse is?
Looking around I found a reference to d3.mouse(this), and that gives me x/y coordinate, but how do I map that back to a data point in the data set?
My goal is to have a tooltip to display some meta-data related to that specific data point in the set.
Here is are some code snippets as requested:
var area=d3.svg.area()
.interpolate("monotone")
.x(function(d){
return(scale.x(d.date));
})
.y0(height-padding.bottom)
.y1(function(d){
return(scale.y(d.count));
});
var path=svg.append('path')
.datum(data)
.attr('d',area)
.attr("clip-path", "url(#clip)")
.attr('fill','url(#gradient)')
// .attr('title','path')
.on('mousemove',function(){
console.log(arguments);
console.log(d3.select(this));
console.log(d3.mouse(this));
});
#nautat has the right answer in his edit, but I'd like to expand on it because for whatever reason the blocks examples rarely have comments and can be like unfolding someone else's origami.
This is the relavant part from http://bl.ocks.org/3902569 ... comments along the way are mine
// define a function for mouse move
// this function is wired up to the visualization elsewhere with .on('mousemove', fn)
function mousemove() {
// using the x scale, in this case a d3 time scale
// use the .invert() function to interpolate a date along the scale
// given the x-coordinates of the mouse
var x0 = x.invert(d3.mouse(this)[0]),
// using the interpolated date, find an index in the sorted data
// this would be the index suitable for insertion
i = bisectDate(data, x0, 1),
// now that we know where in the data the interpolated date would "fit"
// between two values, pull them both back as temporaries
d0 = data[i - 1],
d1 = data[i],
// now, examine which of the two dates we are "closer" to
// to do this, compare the delta values
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
// move the "focus" element into position
// we find the X and Y values for the new position using the x and y scales
// using the closest data point to the mouse
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
// set the text of the "focus" element to be the value of the element selected
focus.select("text").text(formatCurrency(d.close));
}
Your problem is not so much related to the mouseover event listener, but more to the way you bind data to your path; you don't do a proper data join.
Read more about data joins: http://bost.ocks.org/mike/join/
The following example is using divs instead of paths, but the principle is the same. See working example at: http://jsfiddle.net/RghQn/
var data = ['a', 'b', 'c'];
d3.select("body").selectAll("div")
.data(data)
.enter().append("div")
.text(String)
.on("mouseover", function(d,i) {
console.log("mouseover!");
// d: bound datum to DOM element
console.log("d: ", d);
// i: index of the selection
console.log("i: ", i);
// this context: the current DOM element
console.log(d3.select(this).text());
});
​​​​​​​​​​​​​​​
See also the API docs section about event listeners: https://github.com/mbostock/d3/wiki/Selections#wiki-on
selection.on(type[, listener[, capture]])
Adds or removes an event listener to each element in the current
selection, for the specified type. The type is a string event type
name, such as "click", "mouseover", or "submit". The specified
listener is invoked in the same manner as other operator functions,
being passed the current datum d and index i, with the this context as
the current DOM element. To access the current event, use the global
d3.event.
EDIT
I know realize I misunderstood your question. You have one path and want to get information about the path coordinates at the location of the mouse.
There is not straightforward. You can see how Mike did it in the following example: http://bl.ocks.org/3902569

Categories