Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3 v6 pointer function not adjusting for scale and translate

Tags:

d3.js

I am upgrading my app from d3 v5 to v6 and am having an issue migrating the d3.mouse functionality. In my app I apply a transform to the top level svg group and use the zoom functionality to zoom and pan (scale and translate). When I double click on the screen I take the mouse position and draw a square.

Now I am replacing the d3.mouse function with d3.pointer. In my double click event I get the mouse position by calling d3.pointer(event). However this function is not producing a position that is relative to where my top level svg group is positioned and scaled. When I remove the translate and scale from the top level group, the position matches up.

In the older version of d3 I could call d3.mouse(this.state.svg.node()) and it would produce the exact position I clicked corrected for pan and scale. Is this available in version 6? If not, is there a clean way I can adjust for this? The new event object is coming through with a host of different position properties: pagex, offsetx, screenx, x. None of these is producing the position I clicked on. Is there a clean way to acheive this?

like image 892
afriedman111 Avatar asked Oct 03 '20 22:10

afriedman111


1 Answers

You could specify a container element which would factor in a zoom transform in v5 and earlier:

d3.mouse(container)

Returns the x and y coordinates of the current event relative to the specified container. The container may be an HTML or SVG container element, such as a G element or an SVG element. The coordinates are returned as a two-element array of numbers [x, y]. (source)

In d3v6 you can specify this by using the second parameter of d3.pointer:

d3.pointer(event[, target])

Returns a two-element array of numbers [x, y] representing the coordinates of the specified event relative to the specified target. event can be a MouseEvent, a PointerEvent, a Touch, or a custom event holding a UIEvent as event.sourceEvent. ... If the target is an SVG element, the event’s coordinates are transformed using the inverse of the screen coordinate transformation matrix. If the target is an HTML element, the event’s coordinates are translated relative to the top-left corner of the target’s bounding client rectangle. (source)

So as far as I'm aware, you should be use:

d3.pointer(event,this.state.svg.node());

Instead of

d3.mouse(this.state.svg.node());

Here's a d3v6 example:

var svg = d3.select("body")
  .append("svg")
  .attr("width", 500)
  .attr("height", 200);
  
var rect = svg.append("rect")
 .attr("width",500)
 .attr("height",200)
 .attr("fill", "#eee")
 
var g = svg.append("g");
  
var zoomed = function(event) {
  g.attr("transform", event.transform);
}

rect.call(d3.zoom().on("zoom",zoomed))
 .on("click", function(event) {
    var xy = d3.pointer(event,g.node());
    
    g.append("circle")
      .attr("r", 5)
      .attr("cx", xy[0])
      .attr("cy", xy[1])
      .attr("fill","crimson");
 })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.0.0/d3.min.js"></script>

Adapting this v5 example:

var svg = d3.select("body")
  .append("svg")
  .attr("width", 500)
  .attr("height", 200);
  
var rect = svg.append("rect")
 .attr("width",500)
 .attr("height",200)
 .attr("fill", "#eee")
 
var g = svg.append("g");
  
var zoomed = function() {
  g.attr("transform", d3.event.transform);
}

rect.call(d3.zoom().on("zoom",zoomed))
 .on("click", function() {
    var xy = d3.mouse(g.node());
    
    g.append("circle")
      .attr("r", 5)
      .attr("cx", xy[0])
      .attr("cy", xy[1])
      .attr("fill","crimson");
 })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
like image 74
Andrew Reid Avatar answered Oct 26 '22 20:10

Andrew Reid