Limiting domain when zooming or panning in D3.js
Asked Answered
V

2

9

I have implemented a simple D3.js line chart that can be zoomed and panned. It is based on Stephen Bannasch's excellent example here.

The domain of my data is [0, n] in the x dimension.

How can I limit zooming and panning to this domain using the built-in zoom behavior (i.e. using mousewheel events)?

I want to prevent users from panning past 0 on the lower end or n on the upper end, for example they should never be able to see negative values on the x-axis, and want to limit zooming to the same window.

The examples that I found based on Jason Davies work using extent( [...],[...],[...] ) seem to no longer work in version 2.9.1. Unfortunately, the zoom behavior is currently one of the few features not documented in the otherwise outstanding API documentation.

Any pointers are welcome.

PS. I have posted the same question on the D3.js mailing list but did not get a response: https://groups.google.com/d/topic/d3-js/w6LrHLF2CYc/discussion. Apologies for the cross-posting.

Vickey answered 2/5, 2012 at 22:42 Comment(0)
A
0

You just need to limit the domain on redraw. The following code will prevent the graph from being zoomed out past it's initial domains (as used in http://bl.ocks.org/1182434).

SimpleGraph.prototype.redraw = function() {
  var self = this;
  return function() {

    self.x.domain([Math.max(self.x.domain()[0], self.options.xmin), Math.min(self.x.domain()[1], self.options.xmax)]);

    self.y.domain([Math.max(self.y.domain()[0], self.options.ymin), Math.min(self.y.domain()[1], self.options.ymax)]);

    ....
Americium answered 4/5, 2012 at 21:14 Comment(2)
I know this post is old, but FYI: the link you reference lets me zoom out and pan to my hearts content. (Chrome 29.0)Crellen
The graph zooms while panning using this example.Core
H
6

Sadly, the solution posted by Bill did only half the trick: While it does indeed inhibit the panning, it causes the graph to distort if zoom is applied. It is then usually impossible to return to a properly proportioned and positioned graph.

In the following version the proportions of the axes are maintained, even if scrolling to the borders.

As soon as the scaling hits 100%, the scales' domains are reset to their original position. This guarantees a correct positioning, even if the intermediate steps return illegal parameters for the axes.

While not perfect, I hope this script can help somebody until d3 (re)implements this feature.

# x and y are the scales
# xAxis and yAxis are the axes
# graph is the graph you want attach the zoom to

x0 = x.copy()
y0 = y.copy()

successfulTranslate = [0, 0]

zoomer = d3.behavior.zoom()
  .scaleExtent([1,2])

onZoom = ->
  ev = d3.event # contains: .translate[x,y], .scale
  if ev.scale == 1.0
    x.domain x0.domain()
    y.domain y0.domain()
    successfulTranslate = [0, 0]
  else
    xTrans = x0.range().map( (xVal) -> (xVal-ev.translate[0]) / ev.scale ).map(x0.invert)
    yTrans = y0.range().map( (yVal) -> (yVal-ev.translate[1]) / ev.scale ).map(y0.invert)
    xTransOk = xTrans[0] >= x0.domain()[0] and xTrans[1] <= x0.domain()[1]
    yTransOk = yTrans[0] >= y0.domain()[0] and yTrans[1] <= y0.domain()[1]
    if xTransOk
      x.domain xTrans
      successfulTranslate[0] = ev.translate[0]
    if yTransOk
      y.domain yTrans
      successfulTranslate[1] = ev.translate[1]
  zoomer.translate successfulTranslate

graph.select('g.x.axis').call(xAxis)
graph.select('g.y.axis').call(yAxis)
drawBars()

zoomer.on('zoom', onZoom)

# ...
graph.call(zoomer)
Haeres answered 16/8, 2012 at 23:31 Comment(1)
Almost perfect. The problem I had with this is that it has no effect during zoom actions. If you zoom in near the right limit, put the cursor on the left edge of the visible area, and zoom out: The right edge of the visible area will exceed the expected limit. I think for a complete solution, instead of simply accepting / rejecting the changes to the translation, you would need to go in the other direction. From the domain limits calculate the legal range of translate, and limit the translate values.Jacintojack
A
0

You just need to limit the domain on redraw. The following code will prevent the graph from being zoomed out past it's initial domains (as used in http://bl.ocks.org/1182434).

SimpleGraph.prototype.redraw = function() {
  var self = this;
  return function() {

    self.x.domain([Math.max(self.x.domain()[0], self.options.xmin), Math.min(self.x.domain()[1], self.options.xmax)]);

    self.y.domain([Math.max(self.y.domain()[0], self.options.ymin), Math.min(self.y.domain()[1], self.options.ymax)]);

    ....
Americium answered 4/5, 2012 at 21:14 Comment(2)
I know this post is old, but FYI: the link you reference lets me zoom out and pan to my hearts content. (Chrome 29.0)Crellen
The graph zooms while panning using this example.Core

© 2022 - 2024 — McMap. All rights reserved.