Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fire zoom event (or simulate zoom event)

When I zoom with the mouse, the following function attached to myZoom will be executed:

myZoom.on('zoom', function() {

    someElement.attr('transform', 'translate(' + d3.event.translate[0] + ',' + d3.event.translate[1] + ') scale(' + d3.event.scale + ')');

....
// redraw axes, which should stay where they are at.
....

}

To simulate zoom without mouse or some other pointing device, I can just change the value of the attribute 'transform' above. Easy.

But problem is in this function I actually redraw axes, whose scale is automatically recalculated. Refer to this official documentation from d3:

zoom.y([y])

Specifies an y-scale whose domain should be automatically adjusted when zooming. If not specified, returns the current y-scale, which defaults to null. If the scale's domain is modified programmatically, it should be reassigned to the zoom behaviour.

I need to zoom programmatically (maybe with zoom button). How can I fire zoom event, so that scale of my axes is automatically recalculated?

like image 254
user1592714 Avatar asked Nov 06 '12 09:11

user1592714


3 Answers

Programmatic zoom seems to be a daunting task in the D3 library because the D3 zooming is closely tied to the mouse events. A common instance of programmatic zooming is zooming in or out with a slider control. Surprisingly, I couldn't find a single working example of how to make D3 zooming work with a slider control. After investing some time and effort I developed this working demo which can be found here D3SliderZoom. The key point is to change the transform attribute of a "<g>" SVGElement embedded in an "<svg>" element using the scale value thrown by the slider.

function zoomWithSlider(scale) {
    var svg = d3.select("body").select("svg");
    var container = svg.select("g");
    var h = svg.attr("height"), w = svg.attr("width");

    // Note: works only on the <g> element and not on the <svg> element
    // which is a common mistake
    container.attr("transform",
            "translate(" + w/2 + ", " + h/2 + ") " +
            "scale(" + scale + ") " +
            "translate(" + (-w/2) + ", " + (-h/2) + ")");

}

This method then has to be invoked from the change event of the slider as shown below.

$(function() {
$( "#slider-vertical" ).slider({
    orientation: "vertical",
    range: "min",
    min: 1000,
    max: 10000,
    value: 1000,
    slide: function( event, ui ) {
        zoomWithSlider(ui.value/1000);
    }
});

});

This solution is much more elegant than generating pseudo-mouse scroll event.

like image 58
Debasis Avatar answered Sep 24 '22 21:09

Debasis


I ended up calculating new domain for a new zoom level by myself. With this new domain I could redraw two y-axes. For someone, who has same problem, I post my code. It's very specific to my project, so it might be hard to understand. Just for your interest.

wr.zoomSim = function(sNew) {

    var s = wr.zoom.scale(),
        tx = wr.zoom.translate()[0],
        ty = wr.zoom.translate()[1],
        sReal = sNew / s,
        dtx = wr.width / 2 * (1 - sReal),
        dty = wr.height / 2 * (1 - sReal),
        txNew = sReal * tx + dtx,
        tyNew = sReal * ty + dty,

        a = wr.scaleYBZoom.domain()[0],
        b = wr.scaleYBZoom.domain()[1],
        c = wr.scaleYBZoom.range()[0],
        d = wr.scaleYBZoom.range()[1],
        r = (b-a)/(d-c);

    wr.scaleYBZoom.domain([a + r * ( (c - dty) / sReal - c), a + r * ( (d - dty) / sReal - c)]);

    wr.zoom.scale(sNew);
    wr.zoom.translate([txNew, tyNew]);

    wr.svg2.select('g#bar')
            .attr('transform', 'translate(' + txNew + ',' + tyNew + ') scale(' + sNew + ')');

    wr.svg2.select('g#axisl')
            .call(d3.svg.axis().scale(wr.scaleYBZoom).orient('left'))
        .selectAll('line.tick')
            .attr('x2', wr.width - wr.bar.left - wr.bar.right + 2 * wr.padding);
    wr.svg2.select('g#axisr')
            .call(d3.svg.axis().scale(wr.scaleYBZoom).orient('right'))
        .selectAll('line')
            .remove();

};
like image 33
user1592714 Avatar answered Sep 21 '22 21:09

user1592714


Unfortunately @Debasis's answer didn't work for me, because I wanted to achieve this with a zoom behavior which I was already using with my force layout. After two days of desperation I finally found the solution in this thread:

https://groups.google.com/forum/#!msg/d3-js/qu4lX5mpvWY/MnnRMLz_cnUJ

function programmaticZoom ($svg, $zoomContainer, zoomBehavior, factor) {

    var width = $svg.attr('width');
    var height = $svg.attr('height');

    var newScale = zoomBehavior.scale() * factor;
    var newX = (zoomBehavior.translate()[0] - width / 2) * factor + width / 2;
    var newY = (zoomBehavior.translate()[1] - height / 2) * factor + height / 2;

    zoomBehavior
      .scale(newScale)
      .translate([newX,newY])
      .event($zoomContainer);
}
like image 24
Peter Hudec Avatar answered Sep 20 '22 21:09

Peter Hudec