d3 zoom behaviour is overwriten by mousemove event - d3.js

I am implementing d3.js zoom behaviour on a svg, on the same svg I also need to capture mouse coordinate so I can set position of a tool tips that follow mouse cursor's position.
The problem is that my 'mousemove' event has override d3.js zoom behaviour.
With 'mousemove' event added, zoom behaviour stop working.
It is either I have 'mousemove' event or 'zoom' event, but not both. Any suggestion how to get around this? Thanks.
// bind zoom behavior
selection_svg.call(
d3.behavior.zoom()
.translate([0, 0])
.scale(1)
.scaleExtent([1, 14])
.on('zoom', ()=>{ selection_plotArea.attr('transform', 'translate('+d3.event.translate+')scale('+d3.event.scale+')'); })
);
// tool tip coordinate
const toolTipNode = selection_toolTip.node();
toolTipNode.style.position = 'absolute';
selection_svg.node().addEventListener('mousemove',function(evt){
const coord_client = {
x:evt.clientX,
y:evt.clientY,
}
toolTipNode.style.left = `${coord_client.x}px`;
toolTipNode.style.top = `${coord_client.y}px`;
}, false);
I have added the code for this problem to fiddle:
https://jsfiddle.net/apollotang/rt9t1vdj/

The problem seems to be related to tooltipNode catching mouse events. By adding some offset to coord_client your problem would be gone.
selection_svg.on('mousemove', function () {
const coord_client = {
x: d3.event.layerX + 10,
y: d3.event.layerY + 10
};
toolTipNode.style.left = `${coord_client.x}px`;
toolTipNode.style.top = `${coord_client.y}px`;
}, false);
Note: I also changed clientX and clientY to layerX and layerY since there was a bug when scrolling changed mouse position and tooltipNode would have been separated from mouse. Besides the event handling code changed to be handled by d3.

If you have a mousemove event attached to your svg, be sure you aren't calling d3.event.stopPropagation() within the mousemove event, or else you will prevent the zoom behavior.

Related

Interactivity on WebGL Canvas - PIXI + D3

I have a data visualisation with pixi.js and d3 - a proof of concept. It should run on WebGL canvas to enable custom shaders.
Current state can be viewed at https://codepen.io/stopyransky/full/vrMxKQ/
The goal is to have following interactivity features:
clicking and dragging sprite to set it's new location
panning
zooming vis to screen (scaling)
I am stuck on panning and zooming, I have tried below solution and events are recognized properly but I am unable to move the graph.
d3.select(app.view)
.call(d3.zoom()
.scaleExtent([0.5, 8])
.on("zoom", function zoomed() {
transform = d3.event.transform;
if(!globalDragging) updateSimulationOnZoom();
})
);
function updateSimulationOnZoom() {
if(d3.event.sourceEvent.type === 'wheel') {
console.log('zooming')
}
if(d3.event.sourceEvent.type === 'mousemove') {
console.log("paning");
data.sprites.forEach(sprite => {
sprite.fx += transform.x
sprite.fy += transform.y
})
}
}
How to implement properly d3 zoom and pan on webgl canvas in this situation?
Answering my question to close this topic - as per my comment:
There is clean way to achieve zooming and panning interactivity with pixi-viewport. It blends nicely with pixi application - acts as stage to which graphics/sprites can be added.

Event issues when rendering three js on a div

I built my scene in three js and everything was working properly when I rendered the scene on the . But now I need the scene to be on a div, 70% of the viewport to be specific, the other 30% being a left side panel with some info. I replaced every window.height and width for the width and height of the div itself, but now the events are all broken. I tried bubbling and capturing but the problem seems to be related with the camera or something in three js, because when I hover on a corner something is happening...but nothing is happening when i hover/click on the 3d globe. I google everything and didn't find an answer or people with similar issues..I'm hopeless, help? Thanks.
Edit: here's a fiddle: https://jsfiddle.net/2L686cnw/
Did you update all for the renderer relevant variables on window resize?
Just like this:
var onWindowResize = function(){
renderWidth = element.offsetWidth;
renderHeight = element.offsetHeight;
camera.aspect = renderWidth/renderHeight;
camera.updateProjectionMatrix();
renderer.setSize(renderWidth, renderHeight);
};
$(window).on('resize', function() {
onWindowResize();
});
The issue sounds like that you get wrong viewport coordinates for your click position. Most three.js examples add event listeners to window or document object and using event.clientX (and clientY) to transform the mouse screen coordinates to viewport coordinates to use it for raycasting.
Having the 3D scene within a separate div covering only part of the page, it should be a bit different:
function onClick(event) {
var mouse = new THREE.Vector2();
mouse.x = (event.offsetX / panelWidth) * 2 - 1;
mouse.y = - (event.offsetY / panelHeight) * 2 + 1;
// do raycasting
}
canvas.addEventListener('click', onClick);

D3 v4: element 'snaps' to previous translate after programmatic transform

function zoomed() { svg.attr("transform", d3.event.transform); }
var zoom = d3.zoom().on("zoom", zoomed);
var svgMain = d3.select('body').append('svg').call(zoom);
var svg = svgMain.append('g') // All the drawing done here
When I translate svg programmatically with svg.call(zoom.translateBy, 100, 100) then drag the element with the mouse, svg transform attribute snaps back to the value from before the drag.
It is almost as if the transform effected by svg.call is not stored or saved, and reverts to the transform stored in d3.event.transform.
This question seems to be hitting on the same issue, though for v3.
Seems like you are applying the zoom behavior to two different nodes - svgMain and svg.
Try running svgMain.call(zoom.translateBy, 100, 100) instead of svg.call(...) and see if it solves the problem.

d3.js v4 programmatic Pan+Zoom. How?

I did a small example of what i am trying to implement, here it is - https://jsfiddle.net/zeleniy/4sgqgcx0/. You can zoom and pan SVG image as usual. But i have no idea how to implement zoom programmatically when user click on "+" and "-" buttons. Especially if he already zoom in/out and pan original image. Could you help me?
On line 13 of the code you will find zoom event handler.
var zoom = d3.zoom()
.on('zoom', function() {
canvas.attr('transform', d3.event.transform);
});
On lines 35 and 39 - zoom event handlers
d3.select('#zoom-in').on('click', function() {
// what here?
});
d3.select('#zoom-out').on('click', function() {
// what here?
});
So when user click on "+" app should zoom in as if mouse were at the center of SVG element. And the same with "-".
Thanks to Fil. Here is an updated version of jsfiddle.
d3.select('#zoom-in').on('click', function() {
// Smooth zooming
zoom.scaleBy(svg.transition().duration(750), 1.3);
});
d3.select('#zoom-out').on('click', function() {
// Ordinal zooming
zoom.scaleBy(svg, 1 / 1.3);
});

HTML 5 Canvas Mouse over event on element (show tooltip)

I am working on a visualization project. Based on my data I am plotting hundreds of small circle on canvas. I want to add a mouse over event so that whenever a mouse is the enclosing area of a circle it will show some node property from my data as a tool tip or as text on the canvas.
My current drawCircle method
function drawCircle(canvas,x,y,r)
{
canvas.strokeStyle = "#000000";
canvas.fillStyle = "#FFFF00";
canvas.lineWidth = 2;
canvas.beginPath();
canvas.arc(x,y,r,0,Math.PI*2,true);
canvas.stroke();
canvas.fill();
canvas.closePath();
}
I have looked into kinetic.js
But can't figure it out how I can call my drawCircle [repetitively] method using their library.
Any help will be highly appreciated.
If you still want to use KineticJS, you would put the Kinetic shape stuff inside your drawCircle routine. This is basically pulled out of their tutorial and stripped down:
function drawCircle(stage,x,y,r) {
var circle = new Kinetic.Shape(function(){
var context = this.getContext();
// draw the circle here: strokeStyle, beginPath, arc, etc...
});
circle.addEventListener("mouseover", function(){
// do something
});
stage.add(circle);
}
If you don't want to use KineticJS after all, you will need to remember for yourself the positions and radii of every circle you drew, and then do something like this:
canvas.onmouseover = function onMouseover(e) {
var mx = e.clientX - canvas.clientLeft;
var my = e.clientY - canvas.clientTop;
// for each circle...
if ((mx-cx)*(mx-cx)+(my-cy)*(my-cy) < cr*cr)
// the mouse is over that circle
}

Resources