Add zoom slider to d3.js

I thought this would be a simple task. However it took a bit of work to get things going. There are a few ways of doing this but here is how I handled the problem (not saying this is the best way). I need to have the mouse scroll wheel and a zoom slider work together. For the slider I used a lightweight library called Dragdealer, the main reason I used a library is because I wanted a vertical slider like google maps, and the HTML5 range input was still not 100%. Although it will likely be a good option soon.

Add a the zoom behavior to the svg (I’m making a force graph, so this might be different)

vis.append("rect")
	.attr("width", "100%")
	.attr("height", "100%")
	.attr("fill", "#F0F0F0")
	.attr("cursor","move")
	.call(d3.behavior.zoom()
	  .on("zoom", function() {
 
		  translatePos=d3.event.translate;
		  var value = zoomWidgetObj.value.target[1]*2;
 
		  //detect the mousewheel event, then subtract/add a constant to the zoom level and transform it
		  if (d3.event.sourceEvent.type=='mousewheel' || d3.event.sourceEvent.type=='DOMMouseScroll'){
				if (d3.event.sourceEvent.wheelDelta){
					if (d3.event.sourceEvent.wheelDelta > 0){
						value = value + 0.1;
					}else{
						value = value - 0.1;
					}
				}else{
					if (d3.event.sourceEvent.detail > 0){
						value = value + 0.1;
					}else{
						value = value - 0.1;
					}
				}
		  }
		transformVis(d3.event.translate,value);
	  })); 
 
vis = vis.append("g");

Here we are adding the zoom behavior and capturing when the mousewheel is used, at least for web-kit and firefox. When it is we grab the zoom level stored in the zoom widget slider value and add/minus a constant value based off of the wheel delta is being scrolled up or down. I’m also storing the mouse x,y from the d3.event.translate into a global so the scroll slider widget has access to them.

Next in the transform function we test if the transform request came from the mouse or the slider, if it is the mouse we call the zoom widget to update their slider position, the doZoom stuff is just a hack to prevent the slider callback from zooming again while it is setting the slider position.

function transformVis(pan,zoom){
 
	if (d3.event){
		if (d3.event.sourceEvent.type == "mousewheel" || d3.event.sourceEvent.type=='DOMMouseScroll'){
		//they scrolled with the mouse wheel, update the slider postion but do not trigger it's transformVis call
			zoomWidgetObj.setValue(0,zoom/2,false,false);
		}else{
			//they are interacting with the slider
			zoomWidgetObj.doZoom = true;
		}
	}else{
		zoomWidgetObj.doZoom = true;
	}
 
	vis.attr("transform", "translate(" +  (pan[0] + ((visWidth - (visWidth * zoomFactor))/2)) + ',' + (pan[1] + ((visHeight - (visHeight * zoomFactor))/2)) + ")scale(" + zoom*zoomFactor + ")"); 	
 
}

zoomFactor is a constant you can set to set the initial zoom level.

And then in the zoom widget or range input update you would call transformVis with its stored value. So basically we are removing d3.event.scale from the equation and moving its value into the zoom widget’s value.

As I said this is a bit hacky but it is a working method for getting both zoom controls while still making sure the scroll wheel works.

  • mikey

    Why not add a demo! That would be appreciated.

  • Bob

    Please add a demo.

  • Jo Kerozen

    I guess I would fully understand this tip with the html code and the js code about the zoomWidgetObj slider
    For now, I can’t manage to make this work :(

  • thisismattmiller

    I can work on adding a simplified example. Here a network I use a zoomslider, slightly diffrent than in this example, but you might be able to get something out of it: 
    http://linkedjazz.org/network/

  • BarryScott

    yes a demo would be helpful, i have been trying to get your code to work and have been unsuccessful, and couldn’t get the jazz demo either 

  • Anna

    Awesome, but demo demo demo, please please please!