Get mouse position in SVG co-ordinates after zoom

2020-04-08 11:54发布

Using D3 v4, SVG, and the zoom behaviour, on mousemove, I want to display the mouse co-ordinates in SVG's co-ordinate system.

The mousemove event only appears to provide client/screen co-ordinates.

How do I convert these co-ordinates into SVG co-ordinates that reflect the current zooom/pan/etc transform?

I can see examples of this using axis/scales/etc but I'm not creating a graph and am not using axis/etc- I'm using D3.js for an interactive diagram.

I've tried the crateSVGPoint/getScreenCTM approach but (a) don't really understand it and am therefore unsure how to apply it to my code, and (b) it doesn't appear to work when I zoom/pan - I just get the client/screen co-ordinates. EG: here's the code in my mousemove event to convert to SVG co-ords:

var theSvg = document.getElementById('svgItem');
var pt = theSvg.createSVGPoint();
var cursorPoint = function(evt){
  pt.x = evt.clientX; pt.y = evt.clientY;
  return pt.matrixTransform(theSvg.getScreenCTM().inverse());
}
var loc = cursorPoint(d3.event);

//the pair of co-ordinates should be different on zoom, but aren't
d3.select(".statusBarText").text("move (" + d3.event.x + "," + d3.event.y + ") (" + loc.x + "," + loc.y + ")");

For what it's worth, the zoom behaviour is applied to an SVG group element of the SVG element. Zooming works fine; I can see the translate/scale being applied to the group.

I've tried to modify the above to call createSVGPoint() on the group element which has been transformed but get errors about createSVGPoint() not being a function; I guess this only works with the SVG element from the DOM...

There's also a viewBox set on the SVG element I'm using, if that makes a difference.

Surely there's got to be an easy way to do the conversion?

1条回答
三岁会撩人
2楼-- · 2020-04-08 12:04

In order to convert the coordinate returned by d3.mouse to the coordinate system used by d3.zoom, you need to get the zoom transform in addition to the coordinates returned by d3.mouse. I'll highlight one method here.

You can get the current zoom transform with:

d3.zoomTransform(selection.node());

Where selection is the d3 selection the zoom is called on. While d3.mouse() gives us the screen coordinates of the mouse relative to the container, we can use that and the transform to give us the scaled and translated coordinates with transform.invert(), which takes a point and will return the zoomed coordinates:

  var xy = d3.mouse(this);         // relative to specified container
  var transform = d3.zoomTransform(selection.node());
  var xy1 = transform.invert(xy);  // relative to zoom

Here's a quick example showing extraction of the zoomed coordinates as compared with the mouse coordinates. The axes are just to give a rough idea of what the zoom coordinate system is (they show zoom coordinates), but the functionality remains the same without them:

var svg = d3.select("body")
  .append("svg")
  .attr("width", 500)
  .attr("height", 300)
  .attr("fill","#eee");
  
var g = svg.append("g")
  .attr("transform","translate(50,50)");
  
var rect = g.append("rect")
  .attr("width",400)
  .attr("height",200)
  .attr("fill","#eee");

var x = d3.scaleLinear().domain([0,400]).range([0,400]);
var y = d3.scaleLinear().domain([0,200]).range([0,200]);

var axisX = d3.axisTop().scale(x).tickSize(-200)
var axisY = d3.axisLeft().scale(y).tickSize(-400);

var gX = g.append("g").call(axisX)
var gY = g.append("g").call(axisY)

var zoom = d3.zoom()
  .scaleExtent([1, 8])
  .on("zoom",zoomed);
  
rect.call(zoom);

rect.on("click", function() {
  var xy = d3.mouse(this);
  
  var transform = d3.zoomTransform(rect.node());
  var xy1 = transform.invert(xy);

  console.log("Mouse:[", xy[0], xy[1], "] Zoomed:[",xy1[0],xy1[1],"]")
})
  
function zoomed() {
  gX.call(axisX.scale(d3.event.transform.rescaleX(x)));
  gY.call(axisY.scale(d3.event.transform.rescaleY(y)));
}
rect {
  cursor: pointer;
}
.tick line {
  stroke: #ccc;
  pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

Of course you must ensure that d3.mouse and d3.zoom are referencing the same thing, in this case the rectangle.

查看更多
登录 后发表回答