Question

I'm using d3 tree layout similar to this example: http://bl.ocks.org/mbostock/4339083

I implemented a search box that when typing, centers your screen on a virtual "average" position of all the appropriate nodes.

Searching for a term bolds and centers item

I want to adjust the scale, so that selected nodes will be

  1. All Visible
  2. As zoomed in as possible.

If the search match is exactly 1, simulate the clicking on the node, else center to this virtual position.

    if (matches[0].length === 1) {
        click(matches.datum(), 0, 0, false);
    }
    else {
        var position = GetAveragePosition(matches);

        centerToPosition(position.x, position.y, 1);
    }

This is what the centerToPosition function looks like:

function centerToPosition(x0, y0, newScale) {

    if (typeof newScale == "undefined") {
        scale = zoomListener.scale();
    }
    else {
        scale = newScale;
    }
    var x = y0 * -1; //not sure why this is.. but it is
    var y = x0 * -1;
    x = x * scale + viewerWidth / 2;
    y = y * scale + viewerHeight / 2;
    d3.select('g').transition()
        .duration(duration)
        .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
    zoomListener.scale(scale);
    zoomListener.translate([x, y]);
}

So how can I calculate the new scale? I tried different variations by taking the extents of the data points

        var xExtent = d3.extent(matches.data(), function (d) {
            return d.x0;
        });
        var yExtent = d3.extent(matches.data(), function (d) {
            return d.y0;
        });

Also tried looking at the transform properties of the group before centering the screen.

var components = d3.transform(svgGroup.attr("transform"));

I'll try to add a js fiddle soon! EDIT: Here it is: http://jsfiddle.net/7SJqC/

Was it helpful?

Solution

Interesting project.

The method of determining the appropriate scale to fit a collection of points is fairly straightforward, although it took me quite a while to figure out why it wasn't working for me -- I hadn't clued in to the fact that (since you were drawing the tree horizontally) "x" from the tree layout represented vertical position, and "y" represented horizontal position, so I was getting apparently arbitrary results.

With that cleared up, to figure out the zoom you simply need to find the height and width (in data-coordinates) of the area you want to display, and compare that with the height and width of the viewport (or whatever your original max and min dimensions are).

ScaleFactor = oldDomain / newDomain

Generally, you don't want to distort the image with different horizontal and vertical scales, so you figure out the scale factor separately for width and height and take the minimum (so the entire area will fit in the viewport).

You can use the d3 array functions to figure out the extent of positions in each direction, and then find the middle of the extent adding max and min and dividing by two.

        var matches = d3.selectAll(".selected");

        /*...*/

        if ( matches.empty() ) { 
            centerToPosition(0, 0, 1); //reset
        }
        else if (matches.size() === 1) {
            click(matches.datum(), 0, 0, false);
        }
        else {
            var xExtent = d3.extent(matches.data(), function (d) {
                return d.x0;
            });
            var yExtent = d3.extent(matches.data(), function (d) {
                return d.y0;
            });

            //note: the "x" values are used to set VERTICAL position,
            //while the "y" values are setting the HORIZONTAL position

            var potentialXZoom = viewerHeight/(xExtent[1] - xExtent[0] + 20);
            var potentialYZoom = viewerWidth/(yExtent[1] - yExtent[0] + 150);
            //The "20" and "150" are for height and width of the labels
            //You could (should) replace with calculated values 
            //or values stored in variables

            centerToPosition( (xExtent[0] + xExtent[1])/2,
                              (yExtent[0] + yExtent[1])/2,
                             Math.min(potentialXZoom, potentialYZoom)
            );
        }

http://jsfiddle.net/7SJqC/2/

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top