Search code examples
javascripthtml5-canvas

Zoom HTML5 canvas context to bounding box


I have arbitrary data points with x/y coordinates that I want to draw on an HTML5 canvas. I would like to zoom the canvas context to fit the data points. How can I adjust the canvas scale and translation to fit a specific bounding box?

My searches for this topic show various hits using different libraries like d3.js, or geospatial databases, but I'd like a pure JavaScript/HTML5 solution. I can (have) hand-written combinations of ctx.scale() and ctx.translate() that via hand-tweaking can match a particular set of points, but I want a general solution that I can feed a bounding box (e.g. x,y,w,h) to and have the context transformed.


Solution

  • Here is a function that takes an HTML canvas context and a bounding box—specified as minX, minY, either maxX/maxY or width/height, and an optional padding value—and calculates the scale and translation needed to make the canvas drawing area focus only on that box.

    I've assumed that a symmetric scale is desired, with extra content shown on the sides or top/bottom, based on aspect ratio.

    function zoomContext(ctx, bbox={minX:0, minY:0, maxX:10, maxY:20, padding:0}) {
        let {minX:x, minY:y, maxX, maxY, width:w, height:h, padding} = bbox
        w ||= maxX - x
        h ||= maxY - y
        if (padding) {
            x -= padding
            y -= padding
            w += padding*2
            h += padding*2
        }
        const cx = x + w / 2
              cy = y + h / 2,
              sx = ctx.canvas.width / w,
              sy = ctx.canvas.height / h
        const symmetricScale = Math.min(sx, sy)
    
        ctx.resetTransform()
        ctx.scale(symmetricScale, symmetricScale)
        ctx.translate(-cx, -cy)
    }
    

    Before calling this you may want to ensure that the internal height and width of the canvas match its display size (in case you are scaling the canvas using CSS):

    myCanvas.width = myCanvas.offsetWidth
    myCanvas.height = myCanvas.offsetHeight