A simple example showing how to limit panning to drawing area.
Built with blockbuilder.org
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<style>
body {
margin:0;
position: fixed;
top:0;
right:0;
bottom:0;
left:0;
}
.circle {
fill: orange;
fill-opacity: 0.7;
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<script>
"use strict";
const margin = {top: 40, right: 40, bottom: 40, left: 40};
const width = 500 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;
const data = d3.range(100).map(d => {
return {x: Math.random()*width, y: Math.random()*height}
});
let x = d3.scale.linear()
.domain([0, width])
.range([0, width]);
let y = d3.scale.linear()
.domain([0, height])
.range([height, 0]);
const xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
const yAxis = d3.svg.axis()
.scale(y)
.orient('left');
const zoomed = function () {
console.log('zoom');
let e = d3.event;
let tx = Math.min(0, Math.max(e.translate[0], width - width*e.scale));
let ty = Math.min(0, Math.max(e.translate[1], height - height*e.scale));
zoom.translate([tx,ty]);
main.selectAll('.circle').attr('transform', 'translate(' + [tx,ty] + ')scale(' + e.scale + ')');
svg.select('.x.axis').call(xAxis);
svg.select('.y.axis').call(yAxis);
}
const zoom = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([1,8])
.on('zoom', zoomed);
const svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.call(zoom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'x axis')
.call(xAxis);
svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
// Clipping
svg.append('defs')
.append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', width)
.attr('height', height);
const main = svg.append('g')
.attr('class', 'main')
.attr('clip-path', 'url(#clip)');
let circles = main.selectAll('.circle').data(data).enter();
circles.append('circle')
.attr('class', 'circle')
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('r', 5);
</script>
</body>
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js