Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How "d3.translateExtent" coordinate works?

jsfiddle DEMO

I am trying to add a drag to the circle and trying to apply a translateExtent. So how to restrict the drag boundary to the rectangle .?

var height = 500;
var width = 500;


//if extent is specified, sets the translate extent to the specified array of points [[x0, y0], [x1, y1]], where [x0, y0] is the top-left corner of the world and [x1, y1] is the bottom-right corner of the world, and returns this zoom behavior. 
var zoom = d3.zoom()
   .translateExtent([[100, 100], [400, 400]])
    .on("zoom", zoomed);

    // Feel free to change or delete any of the code you see in this editor!
    var svg = d3.select("body")
        .append("svg")
        .attr("width", height)
        .attr("height", width)
        .append("g")

svg.append("rect")
        .attr("x", 100)
        .attr("y", 100)
        .attr("height", 300)
        .attr("width", 300);

    var circle = svg.append("circle")
        .attr("cx", 100)
        .attr("cy", 100)
        .attr("r", 20)
        .style("fill", "red")

    svg.call(zoom);

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

Any detailed explanation of how the https://github.com/d3/d3-zoom#zoom_translateExtent works ? How is the boundary calculated from the coordinates.

like image 780
Sarath Avatar asked Dec 13 '18 22:12

Sarath


1 Answers

There are a few considerations here, and given I've certainly been tripped up by them in the past, I hope I can explain them clearly here.

Zoom Extent

Let's look at zoom extent (zoom.extent) - not translate extent. The default extent is "[[0, 0], [width, height]] where width is the client width of the element and height is its client height" (d3-zoom docs). Since you are calling the zoom on the svg, the default extent should be [0,0],[width,height], where width and height in your case are 500 each.

Your translate extent, [100,100],[400,400] is smaller than your zoom extent, this doesn't work, from Mike Bostock on a similar issue : "The problem is that the translateExtent you’ve specified is smaller than the zoom extent. So there’s no way to satisfy the requested constraint." (d3-zoom issue tracker).

TranslateExtent

The issue then, is that you are using translate extent incorrectly. The translate extent you have specified is the bounds that you want the circle to constrained to. But this is not equal to the translate extent, which is the bounds of the coordinate space you want to show (the bounds of the world in which the circle resides) given a zoom extent.

Let's consider the circle at [100,100], it is centered there with a zoom transfrom with translate(0,0): it is at its starting position. This marks the top left position for the bounding box that you hope to constrain the circle in. The top left coordinate of the zoom at this point is [0,0]. The bottom right of the zoom extent or viewport is [500,500].

If the circle is at [400,400], the bottom right of its intended movement, it is has a transform of translate(300,300) as it is 300 pixels right and 300 pixels down from where it started (originally positioned with cx/cy). Given everything is shifted 300 pixels down and right, the top left of the viewport or zoom extent is now [-300,-300] (a circle with cx,cy of -300 would have its center at the top left corner of the SVG given the zoom transform). And the bottom right is [200,200].

To start, when the circle cannot move further up or left, we have a shown extent of [0,0],[500,500], and when the circle is in the bottom right, when the circle cannot move further down or right, we have a shown extent of [-300,-300],[200,200].

Taking the extremes, the maximum extent we want then is: [-300,-300],[500,500], this is the extent of the world we want to show so that the circle remains overlapping with the rectangle:

var height = 500;
var width = 500;

var zoom = d3.zoom()
   .translateExtent([[-300, -300], [500, 500]])
    .on("zoom", zoomed);

var svg = d3.select("body")
        .append("svg")
        .attr("width", height)
        .attr("height", width)
        .append("g")
        
svg.append("rect")
        .attr("x", 100)
        .attr("y", 100)
        .attr("height", 300)
        .attr("width", 300);
           
    var circle = svg.append("circle")
        .attr("cx", 100)
        .attr("cy", 100)
        .attr("r", 20)
        .style("fill", "red")
        
    svg.call(zoom);
    
    function zoomed() {
      circle.attr("transform", d3.event.transform);
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Possible Refinement

If we use a zoom extent with width and height equal to the width and height of the rectangle:

.extent([[0,0],[300,300]])

We don't have to extend our translateExtent to account for the empty space around the rectangle that is still within the SVG:

.translateExtent([[-300,-300],[300,300]])

var height = 500;
var width = 500;


//if extent is specified, sets the translate extent to the specified array of points [[x0, y0], [x1, y1]], where [x0, y0] is the top-left corner of the world and [x1, y1] is the bottom-right corner of the world, and returns this zoom behavior. 
var zoom = d3.zoom()
    .translateExtent([[-300,-300],[300,300]])
    .extent([[0,0],[300,300]])
    .on("zoom", zoomed);
    
    console.log(zoom.extent());
    
    // Feel free to change or delete any of the code you see in this editor!
    var svg = d3.select("body")
        .append("svg")
        .attr("width", height)
        .attr("height", width);
        
     svg.append("rect")
        .attr("x", 100)
        .attr("y", 100)
        .attr("height", 300)
        .attr("width", 300);
           
    var circle = svg.append("circle")
        .attr("cx", 100)
        .attr("cy", 100)
        .attr("r", 20)
        .style("fill", "red")
        
    svg.call(zoom);
    
    function zoomed() {
      circle.attr("transform", d3.event.transform);
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
like image 121
Andrew Reid Avatar answered Nov 19 '22 14:11

Andrew Reid