d3 / d3-zoom

Pan and zoom SVG, HTML or Canvas using mouse or touch input.
https://d3js.org/d3-zoom
ISC License
507 stars 143 forks source link

d3.zoomIdentity.translate().scale() doesn't respect d3.zoom().translateExtent() #96

Closed Mavromatika closed 7 years ago

Mavromatika commented 7 years ago

Still working on implementing a custom transform to zoom in on specific elements (see previous issue). This time I found that d3.zoomIdentity.translate().scale() does not respect d3.zoom().translateExtent().

See an example below :

<!DOCTYPE html>
<meta charset="utf-8">
<p>Try to bring the leftmost circle to the center by dragging it, then by clicking on it.</p>
<svg></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script>

var w = 500, h = 400;
var svg = d3.select("svg")
            .attr("width",w)
            .attr("height",h)
            .style("border","1px solid black");

svg.append("line").attr("x1",w/2).attr("x2",w/2).attr("y1",0).attr("y2",h).attr("stroke","black");
svg.append("line").attr("x1",0).attr("x2",w).attr("y1",h/2).attr("y2",h/2).attr("stroke","black");

var pointsData = d3.range(0,10,1);

var scalePoints = d3.scalePoint()
                .domain(pointsData)
                .range([0,w]);

var color = d3.scaleSequential(d3.interpolatePiYG);

var points = svg.selectAll("circle")
                .data(pointsData)
                .enter()
                .append("circle")
                .attr("cx",function(d){return scalePoints(d);})
                .attr("cy",h/2)
                .attr("r",10)
                .attr("fill",function(d,i){return color(i/pointsData.length);});

var zoomFunc = d3.zoom()
    .scaleExtent([1, 4])
    .translateExtent([[0,0],[w,h]])
    .on("zoom", zoomed);

svg.call(zoomFunc);

function zoomed(){
    points.attr("transform",d3.event.transform);
}

points.on("click",function(d){
   var scale = 2;
    var transform = d3.zoomIdentity
                    .translate(w/2 - scalePoints(d)*scale , h/2 * (1 - scale))
                    .scale(scale);

    svg.transition().duration(300).call(zoomFunc.transform, transform);
});
</script>

Is this behavior intentional ? I would expect everything to respect a translateExtent set on the zoom function.

mbostock commented 7 years ago

Yes. Per the README on zoom.transform:

This method requires that you specify the new zoom transform completely, and does not enforce the defined scale extent and translate extent, if any. To derive a new transform from the existing transform, and to enforce the scale and translate extents, see the convenience methods zoom.translateBy, zoom.scaleBy and zoom.scaleTo.

The reason for this behavior is to give you some flexibility in designing your own zoom transitions and interaction. For example, you could allow the user to zoom outside the allowed area with some increasing elasticity during the zoom gesture, and then smoothly transition back to the allowed area when the zoom gesture ends.

Mavromatika commented 7 years ago

Sorry, it had escaped me. Thanks for your answer. Enforcing the translate extent manually wasn't difficult anyway.

abdul-alim commented 5 years ago

@Mavromatika, Can you please describe with an example how you did it manually?