Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3 Preserve scale/translate after resetting range

Tags:

d3.js

I have an <svg> whose width is 100% of its container. When the container is resized, I update the linear xScale.range() to represent the new resized width of the <svg>. Apparently, I then need to reapply the range to my zoom behaviour as outlined in the zoom.x() documentation:

If the scale's domain or range is modified programmatically, this function should be called again. Setting the x-scale also resets the scale to 1 and the translate to [0, 0].

The resetting of the scale and translate is where I have a problem. If I have previously zoomed in before resizing, I call zoom.x( xScale ) and now d3 thinks the chart's scale is 1 and translate is 0,0 therefore, I cannot zoom out or pan.

Is my approach to the way I handle the resize incorrect?

like image 676
Philip Bulley Avatar asked Sep 16 '14 17:09

Philip Bulley


2 Answers

It looks like the best strategy is to cache the scale and translate values, reset, then reapply. For the record, this code (within my resize handler) roughly shows my solution:

// Cache scale
var cacheScale = zoom.scale();

// Cache translate
var cacheTranslate = zoom.translate();

// Cache translate values as percentages/ratio of the full width
var cacheTranslatePerc = zoom.translate().map( function( v, i, a )
{
  return (v * -1) / getFullWidth();
} );

// Manually reset the zoom
zoom.scale( 1 ).translate( [0, 0] );

// Update range values based on resized container dimensions
xScale.range( [0, myResizedContainerWidth] );

// Apply the updated xScale to the zoom
zoom.x( xScale );

// Revert the scale back to our cached value
zoom.scale( cacheScale );

// Overwrite the x value of cacheTranslate based on our cached percentage
cacheTranslate[0] = -(getFullWidth() * cacheTranslatePerc[0]);

// Finally apply the updated translate
zoom.translate( cacheTranslate );


function getFullWidth()
{
  return xScale.range()[1] * zoom.scale();
}
like image 185
Philip Bulley Avatar answered Oct 31 '22 17:10

Philip Bulley


For those that stumbled upon this looking for a v4 solution, using the awesome setup from Philip above that is for v3, I adapted a loosely-based v4 solution. I spread out the variables to explain it the way v3 used to do it (since it makes more sense in v3). v4 does not have the ability to force the X value like v3 did, so you have to calculate out the existing X and then divide by the scale (K). (There may be a better way to do the final calculation + setting it on the zoom, but the d3-zoom documentation is a little confusing on this)

let transform = d3.zoomTransform(node);
let oldFullWidth = (oldWidth * transform.k);
let newFullWidth = (newWidth * transform.k);

// this is the result you want X to be
let newX = -(newFullWidth * ((transform.x * -1) / oldFullWidth));
// this is just deducting from the existing so you can call .translate
let translateBy = (newX - transform.x) / transform.k;
d3.select(node).call(myZoom.transform, transform.translate(translateBy, 0));
like image 44
James Wagoner Avatar answered Oct 31 '22 17:10

James Wagoner