39

I have several graphs set up to zoom on the container and it works great. However, on the initial load, the zoom level is way too close. Is there a method of setting the initial zoom level to avoid having to first zoom out? I am familiar with the .scale() method but have not had any luck implementing it. Is this the way to go or is there something I am missing?

Here is what I have thus far as pertaining to zoom:

var margin = {top: 20, right: 120, bottom: 20, left: 120},
    width = 50000 - margin.right - margin.left,
    height = 120000 - margin.top - margin.bottom;

var x = d3.scale.linear()
    .domain([0, width])
    .range([0, width]);

var y = d3.scale.linear()
    .domain([0, height])
    .range([height, 0]);

var tree = d3.layout.tree()
    .size([height, width])
    .separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });

var diagonal = d3.svg.diagonal()
    .projection(function(d) { return [d.x, d.y]; });

function zoom(d) {        
  svg.attr("transform",
      "translate(" + d3.event.translate + ")"+ " scale(" + d3.event.scale + ")");
}

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.right + margin.left)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
    .attr("pointer-events", "all")
    .call(d3.behavior.zoom()
        .x(x)
        .y(y)
        .scaleExtent([0,8])
        .on("zoom", zoom))
        .append('g');

svg.append('rect')
    .attr('width', width*5)
    .attr('height', height)
    .attr('border-radius', '20')
    .attr('fill', 'sienna');
davcs86
  • 3,926
  • 2
  • 18
  • 30
Jakub Svec
  • 842
  • 2
  • 10
  • 20

6 Answers6

37

D3v4 answer

If you are here looking for the same but with D3 v4,

var zoom = d3.zoom().on("zoom", function(){
    svg.attr("transform", d3.event.transform);
});

vis = svg.append("svg:svg")
     .attr("width", width)
     .attr("height", height)
     .call(zoom) // here
     .call(zoom.transform, d3.zoomIdentity.translate(100, 50).scale(0.5))
     .append("svg:g")
     .attr("transform","translate(100,50) scale(.5,.5)");
Hartley Brody
  • 8,669
  • 14
  • 37
  • 48
davcs86
  • 3,926
  • 2
  • 18
  • 30
  • 4
    Can't get this solution to work on d3 v5. Do you know if something changed? – Le Sparte Aug 22 '20 at 08:08
  • this solution worked for me well! @LeSparte in the latest version of d3 the event object isn't available. you can access it via the element passed in the function: `var zoom = d3.zoom().on('zoom', function(e){ svg.attr('transform', e.transform)}); ` – Soufyane Hedidi Apr 26 '23 at 22:08
36

I finally got this to work by setting both the initial transform and the zoom behavior to the same value.

var zoom = d3.behavior.zoom().translate([100,50]).scale(.5);

vis = svg.append("svg:svg")
     .attr("width", width)
     .attr("height", height)
     .call(zoom.on("zoom",zooming))
           .append("svg:g")
           .attr("transform","translate(100,50)scale(.5,.5)");  
Musakkhir Sayyed
  • 7,012
  • 13
  • 42
  • 65
Pudders
  • 376
  • 3
  • 4
17

Applies to d3.js v4

This is similar to davcs86's answer, but it reuses an initial transform and implements the zoom function.

// Initial transform to apply
var transform = d3.zoomIdentity.translate(200, 0).scale(1);
var zoom = d3.zoom().on("zoom", handleZoom);

var svg = d3.select("body")
  .append('svg')
  .attr('width', 800)
  .attr('height', 300)
  .style("background", "red")
  .call(zoom)                       // Adds zoom functionality
  .call(zoom.transform, transform); // Calls/inits handleZoom

var zoomable = svg
  .append("g")
  .attr("class", "zoomable")
  .attr("transform", transform);    // Applies initial transform

var circles = zoomable.append('circle')
  .attr("id", "circles")
  .attr("cx", 100)
  .attr("cy", 100)
  .attr('r', 20);

function handleZoom(){
  if (zoomable) {
    zoomable.attr("transform", d3.event.transform);
  }
};

See it in action: jsbin link

hexnod
  • 171
  • 1
  • 3
15

Adding this answer as an addendum to the accepted answer in case anyone is still having issues:

The thing that made this really easy to understand was looking here

That being said, I set three variables:

scale, zoomWidth and zoomHeight

scale is the initial scale you want the zoom to be, and then

zoomWidth and zoomHeight are defined as follows:

zoomWidth = (width-scale*width)/2
zoomHeight = (height-scale*height)/2

where width and height are the width and height of the "vis" svg element

the translate above is then amended to be:

.attr("transform", "translate("+zoomWidth+","+zoomHeight+") scale("+scale+")")

as well as the zoom function:

d3.behavior.zoom().translate([zoomWidth,zoomHeight]).scale(scale)

What this does is effectively ensures that your element is zoomed and centered when your visualization is loaded.

Let me know if this helps you! Cheers.

deweyredman
  • 1,440
  • 1
  • 9
  • 12
  • 2
    I have zoom levels loaded from JSON, I am trying to recreate the zoom level when the svg loads. Could you post your complete solution? :) – Amiga500 Jan 29 '16 at 09:47
  • 2
    It would be so nice if that link still worked.... Thank goodness for [the Wayback Archive](https://web.archive.org/web/20140706172458/http://truongtx.me/2014/03/13/working-with-zoom-behavior-in-d3js-and-some-notes) – Dan Sep 24 '16 at 02:56
  • updating my original answer to reference the archived link. thanks! – deweyredman Jan 04 '17 at 18:56
  • in my case `zoomWidth` and `zoomHeight` where "offsetLeft" and "offsetTop" – Jodo Aug 19 '17 at 08:03
6

D3JS 6 answer

Let's say that you want your initial position and scale to be x, y, scale respectively.

const zoom = d3.zoom();

const svg = d3.select("#containerId")
    .append("svg")
    .attr("width", width)
    .attr("height", height)
    .call(zoom.transform, d3.zoomIdentity.translate(x, y).scale(scale)
    .call(zoom.on('zoom', (event) => {
        svg.attr('transform', event.transform);
     }))
    .append("g")
    .attr('transform', `translate(${x}, ${y})scale(${k})`);

.call(zoom.transform, d3.zoomIdentity.translate(x, y).scale(scale) makes sure that when the zoom event is fired, the event.transform variable takes into account the translation and the scale. The line right after it handles the zoom while the last one is used to apply the translation and the scale only once on "startup".

lilgallon
  • 555
  • 1
  • 7
  • 14
4

I was using d3 with react and was very frustrated about the initial zoom not working.

I tried the solutions here and none of them worked, what worked instead was using an initial scale factor and positions and then updating the zoom function on the basis of those scale factor and positions

const initialScale = 3;
const initialTranslate = [
  width * (1 - initialScale) / 2,
  height * (1 - initialScale) / 2,
];

const container = svg
  .append('g')
  .attr(
    'transform',
    `translate(${initialTranslate[0]}, ${initialTranslate[1]})scale(${initialScale})`
  );

The zoom function would look something like this

svg.call(
  zoom().on('zoom', () => {
    const transformation = getEvent().transform;
    let {x, y, k} = transformation;
    x += initialTranslate[0];
    y += initialTranslate[1];
    k *= initialScale;

    container.attr('transform', `translate(${x}, ${y})scale(${k})`);
  })
);

If you noticed the getEvent() as a function, it was because importing event from d3-selection was not working in my case. So I had to do

const getEvent = () => require('d3-selection').event;
Prasanna
  • 4,125
  • 18
  • 41