This D3 example demonstrates using the zoom event and limits the bounds of the zooming to a specified domain. It is largely based on /jasondavies/3689931, but with bounds. Most of this bounding is done in the refresh function. You need to zoom in before you can pan or zoom out.
forked from tommct's block: D3 Bounded Zoom
xxxxxxxxxx
<meta charset="utf-8">
<title>Constrained Zoom by Rectangle</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body {
font-family: sans-serif;
}
.noselect {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
svg {
font: 9pt sans-serif;
shape-rendering: crispEdges;
}
rect {
fill: #ddd;
}
rect.zoom {
stroke: steelblue;
fill: #bbb;
fill-opacity: 0.5;
}
.axis path, .axis line {
fill: none;
stroke: #fff;
}
</style>
<p><label for="zoom-rect"><input type="checkbox" id="zoom-rect"> zoom by rectangle</label>
<script>
var margin = {top: 0, right: 12, bottom: 20, left: 60},
width = 960 - margin.left - margin.right,
height = 430 - margin.top - margin.bottom;
var xmin = 0,
xmax = 500,
ymin = 0,
ymax = 1000;
var x = d3.scaleLinear()
.domain([xmin+.5, xmax+.5])
.range([0.5, width+.5]);
var y = d3.scaleLinear()
.domain([ymin+.5, ymax+.5])
.range([height+.5, 0.5]);
var xAxis = d3.axisBottom()
.scale(x)
.tickSize(-height);
var yAxis = d3.axisLeft()
.scale(y)
.ticks(5)
.tickSize(-width); // tickLine == gridline
var refresh = function() {
var reset_s = 0;
//var zt = d3.event.transform;
//console.log(zt);
if ((x.domain()[1] - x.domain()[0]) >= (xmax - xmin)) {
//zoom.x(x.domain([xmin, xmax]));
reset_s = 1;
}
if ((y.domain()[1] - y.domain()[0]) >= (ymax - ymin)) {
//zoom.y(y.domain([ymin, ymax]));
reset_s += 1;
}
if (reset_s == 2) { // Both axes are full resolution. Reset.
zoom.transform(d3.select("#main"), d3.zoomIdentity);
}
else {
if (x.domain()[0] < xmin) {
x.domain([xmin, x.domain()[1] - x.domain()[0] + xmin]);
}
if (x.domain()[1] > xmax) {
var xdom0 = x.domain()[0] - x.domain()[1] + xmax;
x.domain([xdom0, xmax]);
}
if (y.domain()[0] < ymin) {
y.domain([ymin, y.domain()[1] - y.domain()[0] + ymin]);
}
if (y.domain()[1] > ymax) {
var ydom0 = y.domain()[0] - y.domain()[1] + ymax;
y.domain([ydom0, ymax]);
}
}
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
}
var zoom = d3.zoom()
.scaleExtent([.001, Infinity]).on("zoom", refresh);
var zoomRect = false;
d3.select("#zoom-rect").on("change", function() {
zoomRect = this.checked;
});
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("id", "main")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom)
.append("g")
.on("mousedown", function() {
if (!zoomRect) return;
var e = this,
origin = d3.mouse(e),
rect = svg.append("rect").attr("class", "zoom");
d3.select("body").classed("noselect", true);
origin[0] = Math.max(0, Math.min(width, origin[0]));
origin[1] = Math.max(0, Math.min(height, origin[1]));
d3.select(window)
.on("mousemove.zoomRect", function() {
var m = d3.mouse(e);
m[0] = Math.max(0, Math.min(width, m[0]));
m[1] = Math.max(0, Math.min(height, m[1]));
rect.attr("x", Math.min(origin[0], m[0]))
.attr("y", Math.min(origin[1], m[1]))
.attr("width", Math.abs(m[0] - origin[0]))
.attr("height", Math.abs(m[1] - origin[1]));
})
.on("mouseup.zoomRect", function() {
d3.select(window).on("mousemove.zoomRect", null).on("mouseup.zoomRect", null);
d3.select("body").classed("noselect", false);
var m = d3.mouse(e);
m[0] = Math.max(0, Math.min(width, m[0]));
m[1] = Math.max(0, Math.min(height, m[1]));
if (m[0] !== origin[0] && m[1] !== origin[1]) {
//zoom.x(x.domain([origin[0], m[0]].map(x.invert).sort(function(a,b) { return a - b;})))
//.y(y.domain([origin[1], m[1]].map(y.invert).sort(function(a,b) { return a - b;})));
x.domain([origin[0], m[0]].map(x.invert, x));
y.domain([origin[1], m[1]].map(y.invert, y));
//var dx = m[0] - origin[0];
//var dy = m[1] - origin[1];
//var x = (origin[0] + m[0]) / 2;
//var y = (origin[1] + m[1]) / 2;
//var scale = Math.max(width / dx, height / dy);
//var translate = [width / 2 - scale * x, height / 2 - scale * y];
//var zt = d3.zoomIdentity
// .scale(scale)
// .translate(-origin[0], translate[1]);
//zoom.transform(d3.select("#main"), zt)
}
rect.remove();
refresh();
}, true);
d3.event.stopPropagation();
});
svg.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
</script>
Modified http://d3js.org/d3.v4.min.js to a secure url
https://d3js.org/d3.v4.min.js