score:2

Accepted answer

For the first approach you need to invert the zoom prior to inverting the xy coordinates into long lat coordinates:

var transform = d3.zoomTransform(this);
var xy = transform.invert(d3.mouse(this));         
var longlat = projection.invert(xy);

We get the mouse position in pixel coordinates, invert it to zoom coordinates, and then invert that to geographic coordinates.

This should demonstrate the above:

var width = 960;
var height = 500;

var canvas = d3.select("canvas");
var context = canvas.node().getContext("2d")
var projection = d3.geoMercator();
var path = d3.geoPath(projection,context);
	

d3.json("https://unpkg.com/world-atlas@1/world/110m.json", function(error, world) {
  if (error) throw error;
  
renderFeature();

var zoom = d3.zoom()
    .scaleExtent([1, Infinity])
    .on("zoom", zoomByContext);

canvas.call(zoom);
	
  function zoomByContext() {
    var transform = d3.event.transform;
    context.clearRect(0, 0, width, height);
    context.save();
    context.translate(transform.x, transform.y);
    context.lineWidth = 0.5 / transform.k;
    context.scale(transform.k, transform.k);
    renderFeature();
    context.restore();
  }
  function renderFeature() {
    context.beginPath();
    path(topojson.mesh(world));
    context.stroke();	
  }
  
  canvas.on("click", function() {
	var transform = d3.zoomTransform(this);
	var xy = transform.invert(d3.mouse(this));         
	var longlat = projection.invert(xy);
    console.log(longlat);
  })

  
  
});
<canvas width="960" height="500"></canvas>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/topojson-client@3"></script>

The second approach is a little trickier, if the projection's translate is [0,0] the approach you have will work, however this is rarely the case. The default value is [480,250] (assumes a canvas that is 960x500), and fitSize and fitExtent don't position the feature by modifying rotate and center, but rather translate. So you need to account for the initial translate when modifying the projection (just as you have done with scale):

var transform = d3.event.transform;
projection.translate([transform.x+translate[0]*transform.k, transform.y+translate[1]*transform.k]);
projection.scale(scale * transform.k);

Here translate is an array holding the initial translate values

And here's a demonstration that should demonstrate the above:

var width = 960;
var height = 500;

var canvas = d3.select("canvas");
var context = canvas.node().getContext("2d")
var projection = d3.geoMercator().center([105,3]).scale(1200).translate([2000,0]);
var path = d3.geoPath(projection,context);
var scale = projection.scale();
var translate = projection.translate();



d3.json("https://unpkg.com/world-atlas@1/world/110m.json", function(error, world) {
  if (error) throw error;

renderFeature();
  
var zoom = d3.zoom()
    .scaleExtent([0.1, Infinity])
    .on("zoom", zoomByProjection);

canvas.call(zoom);
	
function zoomByProjection() {
    context.clearRect(0, 0, width, height);
    var transform = d3.event.transform;
    projection.translate([transform.x+translate[0]*transform.k, transform.y+translate[1]*transform.k]);
    projection.scale(scale * transform.k);
    renderFeature();
}
  
  function renderFeature() {
    context.beginPath();
    path(topojson.mesh(world));
    context.stroke();	
  }

});
<canvas width="960" height="500"></canvas>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/topojson-client@3"></script>


Related Query

More Query from same tag