I have a project where I'm trying to zoom to specific points. In d3v3, I could do that with this function
function zoomTo(place, pscale) {
projection = d3.geo.mercator()
.scale((1 << pscale) / 2 / Math.PI)
.translate([width / 2, height / 2])
center = projection(place);
return zoom
.scale(projection.scale() * 2 * Math.PI)
.translate([width - center[0], height - center[1]]);
}
Zoom has changed, though, and this no longer works in d3v6.
I found an example on Observable that does exactly what I want using d3v6: https://observablehq.com/d/9035ed7049aaa8c6
However, I'm not sure how to translate it to vanilla javascript. Below is my attempt. I'm getting this error:
d3.zoom(...).scaleExtent(...).on(...).center is not a function
It runs fine in Observable, though, so clearly I'm missing something. Any help would be greatly appreciated! (Also, I don't need the buttons that are at the top of the Observable example, so I commented those out.)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Zoom to Point</title>
<script src="https://files-7y4qaclbi.vercel.app/d3-zoom.js"></script>
<script src="https://d3js.org/d3.v6.min.js"></script>
</head>
<body>
<script>
// const form = html`<form>
// <button name="in" type="button">Zoom In</button>
// <button name="out" type="button">Zoom Out</button>
// <button name="random" type="button">Random</button>
// <button name="reset" type="button">Reset</button>
// </form>`;
// form.in.onclick = () => chart.zoomIn();
// form.out.onclick = () => chart.zoomOut();
// form.random.onclick = () => chart.zoomRandom();
// form.reset.onclick = () => chart.zoomReset();
// return form;
const radius = 6
const step = radius * 5
const width = 500
const height = 500
const theta = Math.PI * (3 - Math.sqrt(5))
const data = Array.from({ length: 1000 }, (_, i) => {
const radius = step * Math.sqrt(i += 0.5), a = theta * i;
return [
width / 2 + radius * Math.cos(a),
height / 2 + radius * Math.sin(a)
];
})
const quadtree = d3.quadtree()
.addAll(data)
// let mutable debug = undefined;
let focussed;
function findCloser(that, coords) {
const p = d3.zoomTransform(that).invert(coords);
focussed = quadtree.find(p[0], p[1]);
return d3.zoomTransform(that).apply(focussed);
}
const zoom = d3
.zoom()
.scaleExtent([1, 40])
.on("zoom", zoomed)
.center(function (event, d) {
// mutable debug = { event, d, r: Math.random() };
let coords = d3.pointer(event, this);
focussed = null;
if (event.deltaY < 0) {
coords = findCloser(this, coords);
}
g.selectAll("circle").attr("r", p =>
p == focussed ? radius + 3 : radius
);
return coords;
});
const svg = d3.select('body').append("svg")
.attr('width', width)
.attr('height', height)
.on("click", reset);
const g = svg.append("g");
g.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", ([x]) => x)
.attr("cy", ([, y]) => y)
.attr("r", radius)
.attr("fill", (d, i) => d3.interpolateRainbow(i / 360))
.on("click", clicked);
svg.datum("I'm your worst datum").call(zoom); //.on("mousemove", mousemove);
function random() {
const [x, y] = data[Math.floor(Math.random() * data.length)];
svg
.transition()
.duration(2500)
.call(
zoom.transform,
d3.zoomIdentity
.translate(width / 2, height / 2)
.scale(40)
.translate(-x, -y)
);
}
function reset() {
svg
.transition()
.duration(750)
.call(
zoom.transform,
d3.zoomIdentity,
d3.zoomTransform(svg.node()).invert([width / 2, height / 2])
);
}
function clicked(event, [x, y]) {
event.stopPropagation();
svg
.transition()
.duration(750)
.call(
zoom.transform,
d3.zoomIdentity
.translate(width / 2, height / 2)
.scale(40)
.translate(-x, -y),
d3.pointer(event, svg.node())
);
}
function zoomed(event) {
g.attr("transform", event.transform);
}
// return Object.assign(svg.node(), {
// zoomIn: () =>
// svg
// .transition()
// .call(zoom.scaleBy, 2, findCloser(svg.node(), [width / 2, height / 2])),
// zoomOut: () => svg.transition().call(zoom.scaleBy, 0.5),
// zoomRandom: random,
// zoomReset: reset
// });
</script>
</body>
</html>
Here it is in a Plunker, as well, if that's helpful: https://plnkr.co/edit/JaiKhTgEXzIFWiar?open=lib%2Fscript.js
There is no zoom.center
in any d3-zoom release, from the first one to the current (v3.0.0 at the time of writing).
That zoom.center
method was created by that Observable's author, which actively contributes to D3 (see here). You can see the proposal here: https://github.com/d3/d3-zoom/pull/212
However, if you inspect the code the author is using, you'll see that adding your own center
method is not complicated. All you need is:
zoom.center = function(_) {
return arguments.length ? (center = typeof _ === "function" ? _ : constant([+_[0], +_[1]]), zoom) : center;
};
...and...
function defaultCenter(event) {
return d3Selection.pointer(event, this);
}
With a couple of center.apply(this, arguments)
that you can find by having a look at that file.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With