3
votes

I've managed to make a d3.js line+area graph sync with focus/context brushing and pan/zoom, with a small example here:

http://jsfiddle.net/MtXvx/8/

I'm having trouble limiting the panning to stop at the original domain boundaries, while also working nicely with the brush. This is to prevent users from losing the graph in their view.

While I have tried manually detecting when panning has exceeded boundaries and then setting zoom.translate([0,0]), such as in these examples: d3.js scatter plot - zoom/drag boundaries, zoom buttons, reset zoom, calculate median Limiting domain when zooming or panning in D3.js d3.js scatter plot - zoom/drag boundaries, zoom buttons, reset zoom, calculate median

...as I do here at line 183:

 //If exceed original domain, limit panning by resetting translate
if (x.domain()[0] < x0.domain()[0]) {
    zoom.translate([0, 0]);
}

The problem occurs when: 1) Create a brush region in the small context graph 2) Pan the big focus graph all the way towards the earliest date 3) Graph jumps when panning is almost at the boundary

Would appreciate any help to prevent the jumping from happening, or if there is any other way to limit the panning (and eventually the zooming out too) to the original domain boundaries.

Regarding limiting the zoom-out, setting:

var zoom = d3.behavior.zoom().x(x).scaleExtent([1,10]).on("zoom", zoomed);

...does not work nicely because the zoom-out would be limited to the brush region instead of the full extent of the graph data.

Much thanks!

2
Did you succeed to do this? can you upload example?Arnon

2 Answers

1
votes

I had similar problems combining D3 Brushing and Zoom & Pan, but figured it out eventually. I found the key to limit the panning is to reset the translate of the zoom behavior object. Specifically, here is my zoom callback function:

function zoomed() {
  var t =   d3.event.translate;
  var s =   d3.event.scale;
  var size = width*s;
  t[0] = Math.min(t[0], 0);
  t[0] = Math.max(t[0], width-size);
  zoom.translate(t);
  focus.select(".area").attr("d", area);
  focus.select(".x.axis").call(xAxis);
  var brushExtent = [x.invert(0), x.invert(width)];
  context.select(".brush").call(brush.extent(brushExtent));
}

While not part of your question, also an important part to make the whole demo work right is to update the zoom translate and scale when brushing is done, so here is my brushed callback:

function brushed() {
  x.domain(brush.empty() ? x2.domain() : brush.extent());
  focus.select(".area").attr("d", area);
  focus.select(".x.axis").call(xAxis);
  var s = x.domain();
  var s_orig = x2.domain();
  var newS = (s_orig[1]-s_orig[0])/(s[1]-s[0]);
  var t = (s[0]-s_orig[0])/(s_orig[1]-s_orig[0]); 
  var trans = width*newS*t;
  zoom.scale(newS);
  zoom.translate([-trans,0]);
}  

Here is a complete example based one of D3 examples: http://bl.ocks.org/sbreslav/be9af0d809b49864b7d8

0
votes

to limit the extent of the panning on the graph you could use clamp, although I couldn't see where or if you were using a scale in that fiddle (actually it didn't appear to be working). Here's a simple example in a fiddle