I'm trying to create a d3 collapsible vertical tree with zoom feature. But zoom feature is not working properly when scrollbar is visible.
Each time the tree collapses or expands, I'm computing the width and height of tree and updating the height and width attributes of my svg to make scrollbars visible and hidden(if the tree size is less than my div(#d3tree)).
let height = Math.max(finalOpts.height, (bottom.y - top.y) + finalOpts.rectHeight),
width = Math.max(finalOpts.width, (right.x - left.x) + finalOpts.rectWidth);
svg.attr("height", height)
.attr("width", width)
.attr("viewBox", [0, 0, width, height]);
If the tree size is less than my div(#d3tree), I'm translating my g to center of my div. Else I'm translating my g to a position calculated based on the x attr of the left most node of the tree.
// to make the whole tree visible onscroll
g.attr('transform',`translate(${(width > finalOpts.width) ? Math.abs(left.x) + (finalOpts.rectWidth / 2) : width / 2}, 0)`);
I'm able to achieve all this. But, if I zoom, x axis is not translate properly. This happens only when scroll bar is visible. When there is no scrollbar zoom is working fine.
Below is the part of code I'm struggling with. I don't understand what value should be given to zoomTranslateX variable. This is commented in the fiddle. Kindly uncomment this for zoom feature.
// ZoomTranslate
//var zoomTranslateX = 0
//zoomBehaviours.translateTo(svg, (width > finalOpts.width) ? zoomTranslateX : 0, (target.getBoundingClientRect().height / 2) - 5);
//svg.call(zoomBehaviours);
What am I missing here or doing wrong? Kindly help. I'm new to d3.js
I'm using d3V6 JS Fiddle link - https://jsfiddle.net/2ervdf1q/
Update
I also found a solution to overcome this problem. I removed the translation applied to g and computed the zoomTranslateX. By computing this correctly, g got translated correctly and the zoom feature was working fine.
let moveToRight = (Math.abs(left.x) + finalOpts.rectWidth / 2 > width / 2) ? true : false,
moveToLeft = (right.x + finalOpts.rectWidth / 2 > width / 2) ? true : false,
zoomTranslateX = 0;
if(width > finalOpts.width) {
zoomTranslateX = (moveToRight) ? - ((width / 2) - Math.abs(left.x) - (finalOpts.rectWidth / 2)) : (moveToLeft) ? ((width / 2) - Math.abs(left.x) - (finalOpts.rectWidth / 2)) : 0;
} else {
zoomTranslateX = (moveToRight) ? - (((width / 2) - right.x) - (finalOpts.rectWidth / 2) - (Math.abs(left.x) - width / 2)) / 2 : (moveToLeft) ? (((width / 2) - Math.abs(left.x)) - finalOpts.rectWidth / 2) - (right.x - width / 2)) / 2 : 0;
}
zoomBehaviours.translateTo(svg, zoomTranslateX, (height / 2) - 5);
Working JS Fiddle : https://jsfiddle.net/6gbhcq3f/
But the accepted solution is the easiest approach to handle this.
Problem
The issue was that you overwrote the existing translation transform with the zoom behavior transform on the g
element. Thereby deleting the existing translation.
Solution
Add a new g
element under svg
and make it the parent of the current g
element. Thereby preserving the existing translation and applying it after the zoom transform.
const zoomGroup = svg
.append("g");
const g = zoomGroup.append("g")
.attr('transform',`translate(${finalOpts.width / 2}, 0)`);
const zoomBehaviours = d3.zoom()
.scaleExtent(finalOpts.zoomScale)
.on('zoom', (event) => zoomGroup.attr('transform', event.transform.toString()));
Example