Implements constrained zooming of an image constructed from a data-driven ImageData object placed onto an HTML5 Canvas. Borrows heavily from https://gist.github.com/mbostock/3074470.
xxxxxxxxxx
<meta charset="utf-8">
<title>Canvas ImageData Zoom</title>
<style>
body {
position: relative;
}
svg,
canvas {
position: absolute;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
<script src="https://d3js.org/d3.v3.min.js"></script>
<body>
<script>
var margin = {top: 10, right: 10, bottom: 20, left: 30},
width = 600,
height = 400;
var ctx;
var imageObj = new Image();
var x = d3.scale.linear()
.domain([0, width])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, height])
.range([height, 0]);
var xmin = x.domain()[0];
var xmax = x.domain()[1];
var ymin = y.domain()[0];
var ymax = y.domain()[1];
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var zoom = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([1, 10])
.on("zoom", refresh);
var color = d3.scale.linear()
.domain([95, 115, 135, 155, 175, 195])
.range(["#0a0", "#6c0", "#ee0", "#eb4", "#eb9", "#fff"]);
var canvas = d3.select("body").append("canvas")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.style("left", margin.left + "px")
.style("top", margin.top + "px")
.style("width", width + "px")
.style("height", height + "px")
.style("position", "absolute");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// We make an invisible rectangle to intercept mouse events for zooming.
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "000")
.style("opacity", 1e-6)
.call(zoom);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.call(removeZero);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.call(removeZero);
// Keep an eye out for "translateExtent" or "xExtent" methods that may be
// added at some point to bound the limits of zooming and panning. Until then,
// this works.
function refresh() {
var t = zoom.translate();
var s = zoom.scale();
var tx = t[0],
ty = t[1];
var xdom = x.domain();
var reset_s = 0;
if ((xdom[1] - xdom[0]) >= (xmax - xmin)) {
zoom.x(x.domain([xmin, xmax]));
xdom = x.domain();
reset_s = 1;
}
var ydom = y.domain();
if ((ydom[1] - ydom[0]) >= (ymax - ymin)) {
zoom.y(y.domain([ymin, ymax]));
ydom = y.domain();
reset_s += 1;
}
if (reset_s == 2) { // Both axes are full resolution. Reset.
zoom.scale(1);
tx = 0;
ty = 0;
}
else {
if (xdom[0] < xmin) {
tx = 0;
x.domain([xmin, xdom[1] - xdom[0] + xmin]);
xdom = x.domain();
}
if (xdom[1] > xmax) {
xdom[0] -= xdom[1] - xmax;
tx = -xdom[0]*width/(xmax-xmin)*s;
x.domain([xdom[0], xmax]);
}
if (ydom[0] < ymin) {
y.domain([ymin, ydom[1] - ydom[0] + ymin]);
ydom = y.domain();
ty = -(ymax-ydom[1])*height/(ymax-ymin)*s;
}
if (ydom[1] > ymax) {
ydom[0] -= ydom[1] - ymax;
ty = 0;
y.domain([ydom[0], ymax]);
}
}
// Reset (possibly) if hit an edge so that next focus event starts correctly.
zoom.translate([tx, ty]);
ctx.drawImage(imageObj,
tx*imageObj.width/width, ty*imageObj.height/height,
imageObj.width*s, imageObj.height*s);
svg.select(".x.axis").call(xAxis).call(removeZero);
svg.select(".y.axis").call(yAxis).call(removeZero);
}
function removeZero(axis) {
axis.selectAll("g").filter(function(d) { return !d; }).remove();
}
d3.json("heatmap.json", function(error, heatmap) {
xmax = heatmap[0].length,
ymax = heatmap.length;
x.domain([0, xmax]);
y.domain([0, ymax]);
d3.select("canvas")
.attr("width", xmax)
.attr("height", ymax)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(drawImage);
// Compute the pixel colors; scaled by CSS.
function drawImage(canvas) {
ctx = canvas.node().getContext("2d");
var img = ctx.createImageData(xmax, ymax);
for (var y = 0, p = -1; y < ymax; ++y) {
for (var x = 0; x < xmax; ++x) {
var c = d3.rgb(color(heatmap[y][x]));
img.data[++p] = c.r;
img.data[++p] = c.g;
img.data[++p] = c.b;
img.data[++p] = 255;
}
}
ctx.putImageData(img, 0, 0);
imageObj.src = canvas.node().toDataURL();
}
});
</script>
Modified http://d3js.org/d3.v3.min.js to a secure url
https://d3js.org/d3.v3.min.js