I cannot work out how to drag a path around the svg object using d3.js
Specifically, I have a normal distribution shape rendered as a path to the svg and I want to be able to click on it and drag it around the svg space (but there is nothing unique about this particular shape etc) .
I have seen examples for points, straight lines and shapes but not a path.
My simplified code is below. Unless I am way off the mark, I suspect the error is with the dragged function right at the bottom.
Javascript:
// width and height for svg object
var w = 500;
var h = 500;
/// setting up svg object
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
// Values for calculating pdf of normal distribution
var sigma = 4;
var mu = 0;
var N = 10;
var step = 0.1;
var dataset = [];
var x;
// creating the pdf of the normal distribution and plotting it for -N to N
var C = 1/(sigma*Math.sqrt(2*Math.PI));
for (x=-N; x < N; x += step) {
var E = (x-mu)/sigma;
E = -(E*E)/2;
var d = C*Math.exp(E);
dataset.push(d);
}
// Scales slightly over fancy, required for features stripped out
var overlap = w*0.1;
var xscale1 = d3.scale.linear().range([0, w/2+overlap]).domain([0, dataset.length-1]).clamp(true);
var xscale2 = d3.scale.linear().range([w/2-overlap, w]).domain([0, dataset.length-1]).clamp(true);
// So specifies the height as max in dataset and it takes up 1/2 the svg
var yscale = d3.scale.linear().domain([0, d3.max(dataset)]).range([h,h/2]);
var area1 = d3.svg.area()
.x(function(d,i) { return xscale1(i); })
.y0(h)
.y1(function(d,i) { return yscale(d); });
// plots filled normal distribution to svg
g1 = svg.append("path")
.datum(dataset)
.attr("class", "area1")
.attr("d", area1)
.attr("opacity",0.75);
// Problem is probably with the below line and related function dragged
d3.select("path.area1").on("drag", dragged);
function dragged() {
var dx = d3.event.dx,
dy = d3.event.dy;
d3.select(this)
.attr("transform", path => "translate(" + dx + "," + dy + ")");
}
Here is a version of your code which implements the drag:
var w = 500;
var h = 250;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
// Values for calculating pdf of normal distribution
var sigma = 4;
var mu = 0;
var N = 10;
var step = 0.1;
var dataset = [];
var x;
// creating the pdf of the normal distribution and plotting it for -N to N
var C = 1/(sigma*Math.sqrt(2*Math.PI));
for (x=-N; x < N; x += step) {
var E = (x-mu)/sigma;
E = -(E*E)/2;
var d = C*Math.exp(E);
dataset.push(d);
}
// Scales slightly over fancy, required for features stripped out
var overlap = w*0.1;
var xscale1 = d3.scale.linear().range([0, w/2+overlap]).domain([0, dataset.length-1]).clamp(true);
var xscale2 = d3.scale.linear().range([w/2-overlap, w]).domain([0, dataset.length-1]).clamp(true);
// So specifies the height as max in dataset and it takes up 1/2 the svg
var yscale = d3.scale.linear().domain([0, d3.max(dataset)]).range([h,h/2]);
var area1 = d3.svg.area()
.x(function(d,i) { return xscale1(i); })
.y0(h)
.y1(function(d,i) { return yscale(d); });
svg.append("path")
.datum(dataset)
.attr("class", "area1")
.attr("d", area1)
.attr("opacity",0.75)
.call(d3.behavior.drag().on("drag", dragged));
function dragged(d) {
// Current position:
this.x = this.x || 0;
this.y = this.y || 0;
// Update thee position with the delta x and y applied by the drag:
this.x += d3.event.dx;
this.y += d3.event.dy;
// Apply the translation to the shape:
d3.select(this)
.attr("transform", "translate(" + this.x + "," + this.y + ")");
}
<body></body>
<script src="https://d3js.org/d3.v3.min.js"></script>
It's actually the exact same way of doing as any other drags on other types of shapes. You just apply the drag behavior on the selected node.
Here is the part in charge of the drag implementation:
svg.append("path")
.datum(dataset)
.attr("d", area1)
...
.call(d3.behavior.drag().on("drag", dragged));
function dragged(d) {
// Current position:
this.x = this.x || 0;
this.y = this.y || 0;
// Update thee position with the delta x and y applied by the drag:
this.x += d3.event.dx;
this.y += d3.event.dy;
// Apply the translation to the shape:
d3.select(this)
.attr("transform", "translate(" + this.x + "," + this.y + ")");
}
The main thing you missed is the fact that the dx and dy you receive from the event are the movements of the mouse (the "delta" of movement). These movements can't become the new position of the shape. They have to be added to the existing x and y current position of the shape.
And here is the same code but for the version 4 of d3:
var w = 500;
var h = 250;
var svg = d3.select("body").append("svg").attr("width", w).attr("height", h)
// Values for calculating pdf of normal distribution
var sigma = 4;
var mu = 0;
var N = 10;
var step = 0.1;
var dataset = [];
var x;
// creating the pdf of the normal distribution and plotting it for -N to N
var C = 1/(sigma*Math.sqrt(2*Math.PI));
for (x=-N; x < N; x += step) {
var E = (x-mu)/sigma;
E = -(E*E)/2;
var d = C*Math.exp(E);
dataset.push(d);
}
// Scales slightly over fancy, required for features stripped out
var overlap = w*0.1;
var xscale1 = d3.scaleLinear().range([0, w/2+overlap]).domain([0, dataset.length-1]).clamp(true);
var xscale2 = d3.scaleLinear().range([w/2-overlap, w]).domain([0, dataset.length-1]).clamp(true);
// So specifies the height as max in dataset and it takes up 1/2 the svg
var yscale = d3.scaleLinear().domain([0, d3.max(dataset)]).range([h,h/2]);
var area1 = d3.area()
.x(function(d,i) { return xscale1(i); })
.y0(h)
.y1(function(d,i) { return yscale(d); });
// plots filled normal distribution to svg
g1 = svg.append("path")
.datum(dataset)
.attr("class", "area1")
.attr("d", area1)
.attr("opacity",0.75)
.call(d3.drag().on("drag", dragged));
function dragged(d) {
// Current position:
this.x = this.x || 0;
this.y = this.y || 0;
// Update thee position with the delta x and y applied by the drag:
this.x += d3.event.dx;
this.y += d3.event.dy;
// Apply the translation to the shape:
d3.select(this)
.attr("transform", "translate(" + this.x + "," + this.y + ")");
}
<body></body>
<script src="https://d3js.org/d3.v4.min.js"></script>
Related
Below example will draw a rectangle, I would like to rotate the rectangle around top left corner (0,500) with anti-clockwise 15 degree and keep to coordinate system not rotate.
test()
function test() {
var margin = {left:10,top:10,right:10,bottom:10}
var width = 300
var height = 300
var svg = d3.select('body').append('svg')
svg.attr('width',width + margin.left + margin.right)
.attr('height',height + margin.top + margin.bottom)
.style('border','2px solid red')
var g = svg.append('g')
.attr('transform',`translate(${margin.left},${margin.top})`)
var xmin = 0
var xmax = 800
var xScale = d3.scaleLinear().range([0, width]);
xScale.domain([xmin,xmax]);
var ymin = 0
var ymax = 800
var yScale = d3.scaleLinear().range([height, 0]);
yScale.domain([ymin,ymax])
var fw = 200
var fh = 500
var data = [[0,0],[0,fh],[fw,fh],[fw,0]]
draw_box(g,'rect',data,xScale,yScale)
function draw_box(g,clzname,arr,xScale,yScale,col='#ffffff') {
var drawrect = function(d) {
var arr = []
arr.push('M')
for (var i=0;i<d.length;i++) {
e = d[i]
arr.push(xScale(e[0]))
arr.push(yScale(e[1]))
if (i == 0) {
arr.push('L')
}
}
arr.push('z')
var path = arr.join(' ')
return path
}
g.selectAll(clzname)
.data([arr])
.join('path')
.attr('class',clzname)
.attr('d',drawrect)
.attr('stroke','black')
.attr('fill',col)
}
}
<script src="https://d3js.org/d3.v6.min.js"></script>
Below is how I creat a bar chart using rects in D3. However, how would I modify that to get the bar chart with path property in d3js?
I have a json file which has the data to be read and am trying to create a bar chart with paths rather than rectangles in d3js.
Json :
[
{
"name": "sam",
"age": 24
},
{
"name": "baby",
"age": 23
},
{
"name": "adu",
"age": 21
},
{
"name": "ja",
"age": 23
},
{
"name": "mack",
"age": 34
}
]
Code:
<script>
d3.json("mydata.json", function (data) {
var canvas = d3.select('body').append('svg')
.attr('width', 500)
.attr('height', 500);
canvas.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('width',function (d) { return d.age * 10; })
.attr('height', 48)
.attr('y', function (d, i) { return i * 50; })
.attr('fill', 'blue');
canvas.selectAll('text')
.data(data)
.enter()
.append('text')
.attr('fill','white')
.attr('y', function (d, i) {
return i* 50 + 24;
})
.text(function (d) {
return d.name;
})
});
</script>
I have searched through many sites. I am unable to get though
You can't assign a d attribute to a rectangle, but you can make a bar chart out of paths rather than rectangles. All you need to do is know the coordinates of the corners of the rectangle. You really only need two corners on opposite sides. If you had top left and bottom right coordinates ([x0,y0] and [x1,y1] respectively) you could do something like:
function drawRect(x0,y0,x1,y1) {
var p1 = x0 + " " + y0;
var p2 = x0 + " " + y1;
var p3 = x1 + " " + y1;
var p4 = x1 + " " + y0;
var l = "L"; // cause I'm lazy.
return "M"+p1+l+p2+l+p3+l+p4+"Z";
}
This moves the cursor to p1, then draws connecting lines from p1 to p2 to p3 to p4 and then returns to the start with Z.
With scaled values this could get a bit verbose either passing scaled parameters or scaling the values in the path making function (as I do below).
This might look like (paths fade in so you can see that they match the rects):
var data = [1,2,3,5,8,3];
var width = 500;
var height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
var x = d3.scaleBand()
.domain(d3.range(data.length))
.range([0,width]);
var y = d3.scaleLinear()
.domain([0,d3.max(data)])
.range([height,0]);
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("width", x.bandwidth())
.attr("height", function(d) { return height-y(d); })
.attr("x", function(d,i) { return x(i); })
.attr("y", function(d) { return y(d); })
svg.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", makeRect)
.attr("fill","orange")
.style("opacity",0)
.transition()
.style("opacity",1)
.duration(1500);
function makeRect(d,i) {
var x0 = x(i);
var y0 = y(d);
var x1 = x(i) + x.bandwidth();
var y1 = height;
var p1 = x0 + " " + y0;
var p2 = x0 + " " + y1;
var p3 = x1 + " " + y1;
var p4 = x1 + " " + y0;
var l = "L";
return "M"+p1+l+p2+l+p3+l+p4+"Z";
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
But, this can't really be a preferred option in most cases. It is more complex than the rectangles for sure. The rectangle should be preferable, unless of course you are trying to do some sort of manipulation on the path that you can't do with a rectangle, or maybe you want rounded edges. Perhaps you are projecting the path on a globe or maybe you want to do some path transition (I've added extra vertices to the rectangle path to smooth the transition, in most cases, ideally the start and end paths of a transition have the same number of vertices):
var data = [1,2,3,5,8,3];
var width = 500;
var height = 200;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
var x = d3.scaleBand()
.domain(d3.range(data.length))
.range([0,width]);
var y = d3.scaleLinear()
.domain([0,d3.max(data)])
.range([height,0]);
svg.selectAll("path")
.data(data)
.enter()
.append("path")
.attr('d', d3.symbol().type( d3.symbols[1]).size(function(d){ return d*100; }) )
.attr("transform","translate(0,"+height/2+")")
.transition()
.attr("transform",function(d,i) { return "translate("+(x(i)+x.bandwidth()/2) + "," + height/2 + ")" })
.attr("fill","steelblue")
.duration(1500)
.transition()
.attr("d", makeRect)
.attr("fill","orange")
.attr("transform","translate(0,0)")
.duration(1500);
//*/
function makeRect(d,i) {
var x0 = x(i);
var y0 = y(d);
var x1 = x(i) + x.bandwidth();
var y1 = height;
var p1 = x0 + " " + y0;
var p2 = x0 + " " + y1;
var p3 = x1 + " " + y1;
var p4 = x1 + " " + y0;
var l = "L";
return "M"+p1+l+p1+l+p1+l+p4+l+p4+l+p4+l+p3+l+p3+l+p3+l+p2+l+p2+l+p2+"Z";
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
I am creating pie chart using d3.js. I would like to create 3 pies with single svg element with animation.
This is working fine for me. But do creating different I am reducing the radius each time using a loop. But the radius not getting changed.
How to solve this?
my code (sample) :
var array1 = [
0,200
]
window.onload = function () {
var width = 660,
height = 200,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var arc = null;
var pie = d3.layout.pie()
.value(function(d) {
return d; })
.sort(null);
function tweenPie(finish) {
var start = {
startAngle: 0,
endAngle: 0
};
var i = d3.interpolate(start, finish);
return function(d) { return arc(i(d)); };
}
var svg1 = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
for( var i = 0; i < 3; i++) {
arc = d3.svg.arc()
.innerRadius(radius - (5*i)) //each time size differs
.outerRadius(radius - (6)*i); //each time size differs
svg1.append('g')
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.datum(array1).selectAll("path")
.data(pie)
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.transition()
.duration(5000)
.attrTween('d', tweenPie)
}
}
Live Demo
There is a single arc variable that is being used in the tweenPie method and in the for loop. Each time through the for loop, the arc variable is set to a new value. The tweenPie method is called for each pie chart after the for loop exits. As a result, all the pie charts are using the same tweenPie method which is using the arc created in the last for loop.
For each pie chart, you need to create a separate tweenPie method with its own arc. For example...
var array1 = [ 0, 200 ]
window.onload = function () {
var width = 660,
height = 200,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var arc = null;
var pie = d3.layout.pie()
.value(function(d) {
return d; })
.sort(null);
function getTweenPie(arc) {
return function (finish) {
var start = {
startAngle: 0,
endAngle: 0
};
var i = d3.interpolate(start, finish);
return function(d) { return arc(i(d)); };
}
}
var svg1 = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
for( var i = 0; i < 3; i++) {
arc = d3.svg.arc()
.innerRadius(radius - (5*i)) //each time size differs
.outerRadius(radius - (6)*i); //each time size differs
svg1.append('g')
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.datum(array1).selectAll("path")
.data(pie)
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.transition()
.duration(5000)
.attrTween('d', getTweenPie(arc))
}
}
I built a visualization on top of http://bl.ocks.org/patricksurry/6621971.
Basically I added d3.geo.circle and d3.svg.arc on the map.
What I observed is when I pan/zoom the map, the circle remains intact, but the arc disappears.
When I inspected the elements in chrome, I saw that the attribute 'd' of arc path vanished, but for circle path, it got updated appropriately.
Can anyone help me understand why the updated projection got applied to circle path element but not in arc. Is there a way to force re-projection of arcs without have to remove and re-create them?
UPDATE 1: Since this question seemed difficult to recreate, and jsfiddle won't not allow uploading a geo-data file , I am posting the source here:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
background-color: lavender;
border: 1px solid black;
}
path {
fill: oldlace;
stroke: #666;
stroke-width: .5px;
}
path.circle {
fill: red;
stroke: #666;
stroke-width: .5px;
}
path.arc1 {
fill: green;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 1600,
height = 400,
rotate = 60, // so that [-60, 0] becomes initial center of projection
maxlat = 83; // clip northern and southern poles (infinite in mercator)
var projection = d3.geo.mercator()
.rotate([rotate,0])
.scale(1) // we'll scale up to match viewport shortly.
.translate([width/2, height/2]);
// find the top left and bottom right of current projection
function mercatorBounds(projection, maxlat) {
var yaw = projection.rotate()[0],
xymax = projection([-yaw+180-1e-6,-maxlat]),
xymin = projection([-yaw-180+1e-6, maxlat]);
return [xymin,xymax];
}
// set up the scale extent and initial scale for the projection
var b = mercatorBounds(projection, maxlat),
s = width/(b[1][0]-b[0][0]),
scaleExtent = [s, 10*s];
projection.scale(scaleExtent[0]);
var zoom = d3.behavior.zoom()
.scaleExtent(scaleExtent)
.scale(projection.scale())
.translate([0,0]) // not linked directly to projection
.on("zoom", redraw);
var path = d3.geo.path()
.projection(projection);
var svg = d3.selectAll('body')
.append('svg')
.attr('width',width)
.attr('height',height)
.attr('id', 'svg')
.call(zoom);
d3.json("js/data/world-110m2.json", function ready(error, world) {
// adding geo paths
svg.selectAll('path')
.data(topojson.feature(world, world.objects.countries).features)
.enter().append('path')
// adding a circle
svg.append("path")
.datum(d3.geo.circle().angle(2).origin([-10, 0]))
.attr("d", path)
.attr("class", "circle");
redraw();
// adding a pie arc
var r = 10;
var p = Math.PI * 2;
var arc1 = d3.svg.arc()
.innerRadius(r - 5)
.outerRadius(r)
.startAngle(0);
var arcData = JSON.parse('[{ "lon" : "0", "lat":"0", "endAngle":"6.4" }]');
var arcs1 = svg.selectAll("path.arc1");
arcs1 = arcs1.data(arcData)
.enter()
.append("path")
.attr("class", "arc1")
.attr("fill", "green")
.attr("transform", function(d, i) { return "translate(" + projection([d.lon, d.lat])[0] + ", " + projection([d.lon, d.lat])[1] + ")"; })
.attr("d", arc1);
});
// track last translation and scale event we processed
var tlast = [0,0],
slast = null;
function redraw() {
if (d3.event) {
var scale = d3.event.scale,
t = d3.event.translate;
console.log(d3.event.scale + " [" +d3.event.translate + "]");
// if scaling changes, ignore translation (otherwise touch zooms are weird)
if (scale != slast) {
projection.scale(scale);
} else {
var dx = t[0]-tlast[0],
dy = t[1]-tlast[1],
yaw = projection.rotate()[0],
tp = projection.translate();
// use x translation to rotate based on current scale
projection.rotate([yaw+360.*dx/width*scaleExtent[0]/scale, 0, 0]);
// use y translation to translate projection, clamped by min/max
var b = mercatorBounds(projection, maxlat);
if (b[0][1] + dy > 0) dy = -b[0][1];
else if (b[1][1] + dy < height) dy = height-b[1][1];
projection.translate([tp[0],tp[1]+dy]);
}
// save last values. resetting zoom.translate() and scale() would
// seem equivalent but doesn't seem to work reliably?
slast = scale;
tlast = t;
}
svg.selectAll('path').attr('d', path);
}
</script>
I finally figured out what was going wrong. I was supposed to apply transformation on arc elements. So basically, in the redraw() method I did:
var scaleRatio = 1;
function redraw() {
if (d3.event) {
var scale = d3.event.scale,
t = d3.event.translate;
//console.log(d3.event.scale + " [" +d3.event.translate + "]");
// if scaling changes, ignore translation (otherwise touch zooms are weird)
if (scale != slast) {
projection.scale(scale);
} else {
var dx = t[0]-tlast[0],
dy = t[1]-tlast[1],
yaw = projection.rotate()[0],
tp = projection.translate();
// use x translation to rotate based on current scale
projection.rotate([yaw+360.*dx/width*scaleExtent[0]/scale, 0, 0]);
// use y translation to translate projection, clamped by min/max
var b = mercatorBounds(projection, maxlat);
if (b[0][1] + dy > 0) dy = -b[0][1];
else if (b[1][1] + dy < height) dy = height-b[1][1];
projection.translate([tp[0],tp[1]+dy]);
}
// save last values. resetting zoom.translate() and scale() would
// seem equivalent but doesn't seem to work reliably?
if(slast==null)
scaleRatio=1;
else
scaleRatio = scaleRatio * (scale/slast);
console.log(slast+'-' + scaleRatio);
slast = scale;
tlast = t;
}
svg.selectAll('path').attr('d', path);
svg.selectAll("path.arc1")
.attr("transform", function(d, i) { return "translate(" + projection([d.lon, d.lat])[0] + ", " + projection([d.lon, d.lat])[1] + ")scale(" + scaleRatio + ")" })
.attr("d", arc1);
}
But, I still have a knowledge gap, as to why svg.path elements require explicit re-projection, unlike d3.geo.path elements . Hope someone helps me on this.
I'm trying to determine coordinates of point in pixels on a map using d3. The ultimate goal is determine which countries are currently visible on the map.
Here's the code I'm using:
method: function() {
var e = $("#" + this.getView().getId());
if (!e || e.length === 0) {
return;
}
this.width = e.width();
this.height = e.height();
this.topojson = window.topojson;
var width = this.width;
var height = this.height;
var centered;
var d3v3 = window.d3v3;
var projection = d3v3.geo.mercator()
.translate([width / 2, height / 2]);
var path = d3v3.geo.path()
.projection(projection);
var svg = d3v3.select("#" + this.byId("map").getId());
svg.selectAll("g").remove();
svg
.attr("width", width)
.attr("height", height);
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height)
.on("click", clicked);
var g = svg.append("g");
d3v3.json(getPath("/110m_admin_0.json"), function(us) {
g.append("g")
.attr("id", "states")
.attr("class", "counties")
.selectAll("path")
.data(topojson.feature(us, us.objects["110m_admin_0_"]).features)
.enter().append("path")
.attr("d", path)
.attr("class", function(d) { return "q"+(Math.floor((Math.random() * 9) + 0)) + "-9"; });
});
var zoom = d3v3.behavior.zoom()
.on("zoom",$.proxy(function() {
var width1 = width/2;
var height1 = height/2;
var xt1 = d3v3.event.translate[0];
var yt1 = d3v3.event.translate[1];
var x = xt1;
var y = yt1;
x+=width1;
y+=height1;
var proj = projection.invert([x,y]);
proj[0]*=-1;
proj[1]*=-1;
var closestCountry = this.closestCountry(proj);
console.log(d3v3.event.scale + " | " + [x,y] + " | " + proj + " | " + closestCountry );
g.attr("transform","translate("+
d3v3.event.translate.join(",")+")scale("+d3v3.event.scale+")");
g.selectAll("path")
.attr("d", path.projection(projection));
},this));
svg.call(zoom);
}
This code works when the zoom level is 1, but fails as soon as the zoom level changes.
Here's a few variations I've tried.
1)
var x = d3v3.event.translate[0];
var y = d3v3.event.translate[1];
x+=width/2;
y+=height/2;
var proj = projection.invert([x,y]);
2)
var x = d3v3.event.translate[0]/d3v3.event.scale;
var y = d3v3.event.translate[1]/d3v3.event.scale;
x+=width/2;
y+=height/2;
var proj = projection.invert([x,y]);
3)
var x = d3v3.event.translate[0];
var y = d3v3.event.translate[1];
x+=width/2*d3v3.event.scale;
y+=height/2*d3v3.event.scale;
var proj = projection.invert([x,y]);
4)
var x = d3v3.event.translate[0]/d3v3.event.scale;
var y = d3v3.event.translate[1]/d3v3.event.scale;
x+=width/2*d3v3.event.scale;
y+=height/2*d3v3.event.scale;
var proj = projection.invert([x,y]);
One thing I have noticed is that whatever the zoom level, projection.invert() for the same [x,y] point (e.g. [1200,600])
always return the same lng/lat. Furthermore, none of the attempts I've made manage to keep [x,y] constant at varying zoom level so there is something else at play here outside of the translation and scale. I suspect it might have something to do with the projection, but I've still not figured out what.
After zoom has been applied, I haven't found a way to go from a point in pixel to a coordinate in lng/lat or from a point in pixel to a country.
That being said, I found an alternate method that works well too. I have a list of all the countries and their centroids in lng/lat. From that list, I resolve their position in pixel and adjust them based on scale and translation. Finally, I find which one is the closest to the center. Here's the code:
closest: function() {
var p2 = [0,0];
var d = [width/2,height/2];
var scale = d3v3.event.scale;
var translate = d3v3.event.translate;
var min = Number.MAX_VALUE;
var minCountry = null;
for ( var key in this.centroids) {
var p1 = projection(this.centroids[key]);
p1[0]*=scale;
p1[1]*=scale;
p1[0]+=translate[0]-d[0];
p1[1]+=translate[1]-d[1];
var distance = Math.sqrt(Math.pow(p2[0] - p1[0],2) + Math.pow(p2[1] - p1[1],2));
if (distance < min) {
min = distance;
minCountry = key;
}
}
return [ minCountry, min ];
}