An example showing why "translateTo" method that respects "translateExtent" would be useful in D3. The zoomable area contains a number of objects. When user clicks on an object, the area is translated to the object's center. However, since "transform" method does not respect "translateExtent" the viewport goes beyond borders.
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
.canvas { fill: lightgrey; }
.object { fill: white; stroke: black; stroke-width: 1.5; cursor: pointer; }
.object:hover { fill: aliceblue; }
</style>
</head>
<body>
<script>
let width = 960
let height = 500
let minObjectSize = 50
let maxObjectSize = 100
let svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height)
let group = svg.append('g')
let canvas = group.append('rect')
.attr('class', 'canvas')
.attr('width', width)
.attr('height', height)
let zoom = d3.zoom()
.scaleExtent([1, 8])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on('zoom', () => {
group.attr('transform', d3.event.transform)
})
group.call(zoom)
let objects = d3.range(0, 10, 1).map((i) => ({
x: d3.randomUniform(maxObjectSize, width - maxObjectSize)(),
y: d3.randomUniform(maxObjectSize, height - maxObjectSize)(),
width: d3.randomUniform(minObjectSize, maxObjectSize)(),
height: d3.randomUniform(minObjectSize, maxObjectSize)()
}))
group.selectAll()
.data(objects).enter().append('rect')
.attr('class', 'object')
.attr('x', (d) => d.x)
.attr('y', (d) => d.y)
.attr('width', (d) => d.width)
.attr('height', (d) => d.height)
.on('click', (d) => {
let t0 = d3.zoomTransform(group.node())
let x = Math.round(width / 2) / t0.k - (d.x + Math.round(d.width / 2))
let y = Math.round(height / 2) / t0.k - (d.y + Math.round(d.height / 2))
let t1 = d3.zoomIdentity.scale(t0.k).translate(x, y)
group.call(zoom.transform, t1)
})
</script>
</body>
https://d3js.org/d3.v4.min.js