Tldr; dragging the SVG causes it to rotate as well as translate.
I am trying to implement dragging and zooming events on an SVG group using D3 (v.4) as part of an Angular service.
this.unitGroup = this.svg.append('g')
.attr('id', 'unitGroup')
.call(this.drag)
.call(this.zoom);
Dragging translates the SVG.
drag = d3.drag()
.on('start', () => {
console.log('drag start');
this.setClickOrigin(d3.event);
})
.on('drag', (d, i, n) => {
const target = d3.select(n[i]).node() as any;
const m = target.getCTM();
const x = d3.event.x - this.clickOrigin.x;
const y = d3.event.y - this.clickOrigin.y;
this.setClickOrigin(d3.event);
this.translate(target, x, y);
});
While zooming rotates the SVG.
zoom = d3.zoom()
.on('zoom', (d, i, n) => {
const target = d3.select(n[i]).node() as any;
const m = target.getCTM();
const b = target.getBBox();
const dir = (d3.event.sourceEvent.deltaY > 0) ? 1 : -1;
this.rotate(target, dir);
});
My original code worked fine. However, integrating it into Angular has thrown up some problems.
The current problem is that when you drag the unitGroup it triggers the zoom event along with the drag event.
The expected behaviour is that:
'click-and-drag' translates the small, dark-grey box in the x and y dimensions.
'mouse-wheel-scroll' rotates the small, dark-grey box around its center.
Here is a Plunker: https://embed.plnkr.co/0GrGG7T79ubpjYa2ChYp/
Actually, what you are seeing here is the expected behaviour.
In D3, d3.zoom() handles not only the zoom but the panning as well. So, the mousemove is being handled by d3.drag() and by the zoom function as well.
As Bostock (D3 creator) once said:
combining these two behaviors* means that gesture interpretation is ambiguous and highly sensitive to position. (*zoom and drag)
Off the top of my head the simplest solution is just checking if you had a "real" zoom (mouse wheel) in the zoom function and, if you didn't (no mouse wheel), return:
if(!d3.event.sourceEvent.deltaY) return;
Here is your plunker with that change only: https://plnkr.co/edit/jz5X4Vm9wIzbKmTQLBAT?p=preview
Related
I need to develop 2 animations on a polygon:
automatic rotation of each segments
the polygon follows the mouse movements
I'm trying to do that with this code:
// onMouseMove
tool.onMouseMove = function (event) {
mousePoint = event.point;
};
// onFrame
view.onFrame = function (event) {
// Animation 1: Automatic circular rotation of each segment
for (var i = 0; i < polygon.segments.length; i++) {
var offsetRotation = new Point(Math.cos(event.time + i * 2), Math.sin(event.time + i * 2)).multiply(10);
polygon.segments[i].point = polygonCached.segments[i].point.add(offsetRotation);
}
// Animation 2: the polygon moves following the mouse movements with transition
var delta = mousePoint.subtract(polygon.position).divide(15);
polygon.position = polygon.position.add(delta);
};
Here is the whole code: https://codepen.io/anon/pen/YMgEEe?editors=0010
With the above code the automatic rotation of each segments works and the polygon doesn't follow the mouse at all and also without the transition.
Instead, commenting the automatic rotation animation it correctly follows the mouse with transition.
To check if transition works I move the mouse cursor outside of browser window and then go back from another point. Now you'll see no transition when the polygon moves.
Where I'm wrong?
Just move the polygonCached as well.
polygonCached.position = polygonCached.position.add(delta);
https://codepen.io/arthursw/pen/LvKPXo
The cached version of the polygon was not moving, so each time you rotated your points, their position was reset.
I have an absolutely positioned leading line within a relatively positioned div. I need to move the leading line horizonatally as the mouse moves using d3. Since d3.event requires the elements to be positioned within g elements, I am trying to use d3.mouse to move the leading line. However, the dragging behaviour is very erratic and flickers too much. I cannot seem to figure out what is causing this.
var drag = d3.behavior
.drag()
.on('drag', function(event) {
dragMoveNew(this);
})
d3.select(".grid").call(drag);
function dragMoveNew(elem) {
var x = d3.mouse(elem)[0];
$(elem).css('left', x);
}
Here is the jsfiddle link
https://jsfiddle.net/u4koenra/
Use d3.event.x
function dragMoveNew(elem) {
//var x = d3.mouse(elem)[0];
var x = d3.event.x;
$(elem).css('left', x);
}
I'm using d3.js library and I'm having a problem implementing what the client needs.
Here's client request they want to "black circles to "follow" the mouse when you hover over it.
I don't know if d3.js library has this kind feature I can only see, on mouse drag.
I've added my sample code in JSFiddle see below:
node.on("mousemove", function(){
var coords = d3.mouse(this);
//node.attr('transform', 'translate(' + coords[0] + ',' + coords[1] + ')';
nodes.call(force.drag);
});
jsFiddle : https://jsfiddle.net/glenmongaya/4pjaeko3/5/
Thanks for you help.
You want a mouse-over to behave like a drag?
node.on("mousemove", function(d){
d3.event.stopPropagation(); // stop the default event handling
d.fixed = true; // fix the moused over node
var coords = d3.mouse(this.parentNode); // get mouse position
d.px = coords[0]; d.py = coords[1]; // set position
force.resume(); // resume layout
});
Updated fiddle.
I'm currently building a menu that is also draggable and I'm using the following on each individual tab:
(mousedown)="dragging = false"
(mousemove)="checkMouseMove($event)"
(mouseup)="changeRoute('forms')"
Change Route looks like this:
changeRoute(routeName: string) {
// manual reset for dragging.
if (this.dragging) {
return;
}
Past this is just my routing and switch statement's that correctly will change route and apply styling etc.
Previously inside the mousemove event I just had dragging = true but the problem with this is that even the slightest movement will mean that a click does not occur when it's likely to be intended to.
My first instinct was that I need to add a buffer to the amount of space it will need to move to be called a drag but given the output events I'm not sure how to achieve this.
checkMouseMove(event) {
console.log(event);
}
This event provides me with the following output:
How can I use these events in conjunction with my checkMouseMove / Component to only change dragging after a reasonable amount of movement?
You can save the last mouse position and calculate the distance the cursor moved.
Based on this distance you can filter small and unwanted drags.
var lastEvent;
function checkMouseMove (event){
var dx = 0;
var dy = 0;
if (lastEvent) {
dx = event.clientX - lastEvent.clientX;
dy = event.clientY - lastEvent.clientY;
}
var d = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
if (d > 5) { /* handle mouse move here */ }
lastEvent = event;
}
You can also use any other non euclidean heuristics like Manhattan or Chebyshev.
I have the following d3 visualisation. The darker colour at the top indicates that a node has been selected. When I mouseover a non selected node it changes opacity so a user can see which node would be selected if I click.
This is achieved via a CSS style sheet and the following js/d3:
nodeSelection.select("circle").on('mouseover', function(e) {
d3.select(this).classed("hover", true);
_this.fireDataEvent("mouseOverNode", this);
});
nodeSelection.select("circle").on('mouseout', function(e) {
d3.select(this).classed("hover", false);
_this.fireDataEvent("mouseOutNode", this);
});
So, far, so good. However, when I drag, the drag function seems to randomly trigger mouse over and mouse out events on the nodes that I am not dragging. This causes the node opacity to flicker. If I watch on the development tools in chrome I can see that this is because it is causing nodes to gain the class "hover". The code above to add this CSS class appears nowhere else, and by use of the console logging, I have confirmed that mouseover and mouseout events are being fired. These nodes are often far from the cursor.
This issue does not occur in Firefox.
UPDATE: I actually managed to fix this almost immediately after posting this. I just explicitly de-register the listeners inside drag start, and re register in drag end. It might still be interesting to some people if they are having similar issues.
My drag function now looks like:
var drag = d3.behavior.drag()
.on("dragstart", function(d) {
console.log("dragstart");
d.dragstart = d3.mouse(this); // store this
d.fixedbeforedrag = d.fixed;
d.fixed=true;
// deregister listeners
nodeSelection.select("circle").on("mouseover", null).on("mouseout", null);
})
.on("drag", function(d) {
d.px = d.x; // previous x
d.py = d.y;
var m = d3.mouse(this);
d.x += m[0] - d.dragstart[0];
d.y += m[1] - d.dragstart[1];
nodeSelection.attr("transform", "translate(" + [d.x, d.y] + ")");
_this.getForce().start();
})
.on("dragend", function(d) {
console.log("dragend");
delete d.dragstart;
d.fixed = d.fixedbeforedrag;
//reregisters listeners
_this.updateSVG();
});