I am trying to pan and zoom to a svg node using d3js. But I cannot get my head around the math here.
If I force the desired zoom level to be 1, then I seem to get it right.
Here's an example:
let svg = d3.select('svg'),
svgW = svg.node().getBoundingClientRect().width,
svgH = svg.node().getBoundingClientRect().height,
svgCentroid = {
x : svgW / 2,
y : svgH / 2
};
// zoom functionality has been applied to this one
let selector = d3.select('#container');
let elem = d3.select('[id="6"]'),
elemBounds = elem.node().getBBox(),
elemCentroid = {
x : elemBounds.x + (elemBounds.width / 2),
y : elemBounds.y + (elemBounds.height / 2)
};
let position = {
x : svgCentroid.x - elemCentroid.x,
y : svgCentroid.y - elemCentroid.y
};
selector.transition()
.duration(750)
.call(this.zoom.transform, d3.zoomIdentity
.translate(position.x, position.y)
// set scale to 1
.scale(1)
);
My first naive thought was "piece of cake". I will just multiply the calculated positions with desired zoom level. But, surprise surprise, that got me terribly wrong.
// failed miserably
selector.transition()
.duration(750)
.call(this.zoom.transform, d3.zoomIdentity
.translate(position.x * 5, position.y * 5)
.scale(5)
);
I've been trying to play around with this example:
https://bl.ocks.org/smithant/664d6cf86e53442d09687b154a9a411d
It pretty much sums up my intentions, but even though it's right there I don't fully understand it and thus it does not work properly with the rest of my code. I guess what confuses me most about this particular example are how the variables have their names declared.
I'd be grateful if someone could point me in the right direction here. How can I achieve this? What is the appropriate math to correctly zoom and pan within an SVG?
Thanks :)
I think that what you're looking for is:
function () {
var t = d3.transform(d3.select(this).attr("transform")),
x = t.translate[0],
y = t.translate[1];
var scale = 10;
svg.transition().duration(3000)
.call(zoom.translate([((x * -scale) + (svgWidth / 2)), ((y * -scale) + svgHeight / 2)])
.scale(scale).event);
}
Where this represents the element. Have a look here for a working example. In the example you'll be able to zoom to element after pressing on it. Also if panning and zooming an svg is all you need to do check out this library. It just works, no maths required :).
Related
My Three.js project uses and OrthographicCamera and OrthographicTrackBallControls for zoom/pan. I'm trying to add functionality to zoom to the cursor position with no luck. First things first, here's how I'm getting mouse position:
var mX = ((event.clientX - offset.left) / renderer.domElement.clientWidth) * 2 - 1;
var mY = -((event.clientY - offset.top) / renderer.domElement.clientHeight) * 2 + 1;
var vector = new THREE.Vector3(mX, mY, 0.5);
vector.unproject(camera);
vector.sub(camera.position);
Through looking on StackOverflow, there seems to be a lot of information on how to do this with PerspectiveCamera, but these methods don't work with OrthographicCamera. I was able to find this example:
https://htmlpreview.github.io/?https://github.com/w3dot0/three.js/blob/973bf1d40ef552dbf19c19654a79f70e2882563d/examples/misc_controls_zoom_to_mouse.html
Which does precisely what I am trying to accomplish, but the code that achieves this is hidden, though I am able to discern that the camera position is being changed.
Another SO question which is similar suggests changing camera.left, camera.right, camera.top and camera.bottom, but I have had no luck with this approach. This approach seems like a possibility, but I dont understand the calculations necessary to get the correct left, right, top and bottom values.
So the way I see it I have two possibilities:
Change camera's left/right/top/bottom to get the correct view rectangle.
Change camera position.
But I don't know how to get the values I need to accomplish either, let alone which is the better approach.
UPDATE 11/16/2018:
I've updated my function to this ( based on https://github.com/w3dot0/three.js/blob/973bf1d40ef552dbf19c19654a79f70e2882563d/examples/misc_controls_zoom_to_mouse.html):
zoomDirection = new THREE.Vector3();
function mousewheel(event) {
event.preventDefault();
var amount = event.deltaY / 100;
var zoom = camera.zoom - amount;
var offset = el.offset();
;
var mX = amount > 0 ? 0 : ((event.clientX - offset.left) / renderer.domElement.clientWidth) * 2 - 1;
var mY = amount > 0 ? 0 : -((event.clientY - offset.top) / renderer.domElement.clientHeight) * 2 + 1;
zoomDirection.set(mX, mY, 0.001)
.unproject(camera)
.sub(camera.position)
.multiplyScalar(amount / zoom);
camera.position.subVectors(camera.position, zoomDirection);
orthographictrackBallControls.target.subVectors(orthographictrackBallControls.target, webGl.zoomDirection);
camera.zoom = zoom;
camera.updateProjectionMatrix();
}
This seems to work at first: the camera zooms into the mouse point, but then the camera starts to "jump" around after a bit of zooming, with the mesh no longer visible on screen.
Something that might help: I have an axis helper in the screen as well that "flips" when it stops working as expected. When the scene is loaded, the X-axis helper point due left, but when I get to the point where the camera jumps and I no longer see the mesh, the X-axis helper flips to point due right.
Also, if I zoom OUT first, I can zoom in further before the mesh disappears. I'm not sure what this all adds up to but I would appreciate any help.
First week back after New Year and it's taken too long to fix this. Six sides of A4 covered with linear algebra results in
if ( factor !== 1.0 && factor > 0.0 ) {
const mX = (event.offsetX / event.target.width ) * 2 - 1;
const mY = -(event.offsetY / event.target.height) * 2 + 1;
const vector1 = new THREE.Vector3(mX, mY, 0);
const vector2 = new THREE.Vector3(0, 0, 0);
vector1.unproject(this.camera);
vector2.unproject(this.camera);
vector1.subVectors(vector1, vector2);
this.camera.zoom /= factor;
vector1.multiplyScalar(factor - 1.0);
this.camera.position.subVectors(this.camera.position, vector1);
this.controls.target.subVectors(this.controls.target, vector1);
this.camera.updateProjectionMatrix();
this.camera.updateMatrix();
}
Note the different calculation of mX, mY so that it is valid for a viewport.
Implementing the D3-library with its zoom function may seem like a good idea for this case. But giving up the three-controls is in a lot of cases not a deal.
If you want a zoom-behavior like in Google Maps, the following code could be helpful:
const cameraPosition = camera.position.clone();
// my camera.zoom starts with 0.2
if (zoomOld !== 0.2) {
const xNew = this.curserVector.x + (((cameraPosition.x - this.curserVector.x) * camera.zoom) /zoomOld);
const yNew = this.curserVector.y + (((cameraPosition.y - this.curserVector.y) * camera.zoom) /zoomOld);
const diffX = cameraPosition.x - xNew;
const diffY = cameraPosition.y - yNew;
camera.position.x += diffX;
camera.position.y += diffY;
controls.target.x += diffX;
controls.target.y += diffY;
}
zoomOld = camera.zoom;
Your other problem could be caused by the frustum. But I don't know, I'm still a newbie with Three xD
d3.js verison 4:
I have a line chart, which should have a rectangle zoom.
I used this example: http://bl.ocks.org/jasondavies/3689931
I don't want to apply the rectangle data to the scales, like in the example
Instead I want to apply this to my normal zoom Element.
For that I have the math:
.on("mouseup.zoomRect", function() {
d3.select(window).on("mousemove.zoomRect", null).on("mouseup.zoomRect", null);
d3.select("body").classed("noselect", false);
var m = d3.mouse(e);
m[0] = Math.max(0, Math.min(width, m[0]));
m[1] = Math.max(0, Math.min(height, m[1]));
if (m[0] !== origin[0] && m[1] !== origin[1]) {
//different code here
//I have the scale factor
var zoomRectWidth = Math.abs(m[0] - origin[0]);
scaleFactor = width / zoomRectWidth;
//Getting the translation
var translateX = Math.min(m[0], origin[0]);
//Apply to __zoom Element
var t = d3.zoomTransform(e.node());
e.transition()
.duration(that.chart.animationDuration)
.call(that.chart.zoomX.transform, t
.translate(translateX, 0)
.scale(scaleFactor)
.translate(-translateX, 0)
);
}
rect.remove();
refresh();
}, true);
So I actually get the scaleFactor right and it zooms in smoothly.
Only problem is, that I don't seem to get the translation correct.
So it zooms in to the wrong position.
So, now I got it right:
All transformations by earlier zooms need to be undone.
so that k = 1, x = 0, y = 0;
This is the d3.zoomIdentity.
From that point the current zoom needs to be applied and afterwards the translation.
After that the old transform needs to be applied, first translate and then scale
var t = d3.zoomTransform(e.node());
//store x translation
var x = t.x;
//store scaling factor
var k = t.k;
//apply first rect zoom scale and then translation
//then old translate and old zoom scale
e.transition()
.call(that.chart.zoomX.transform, d3.zoomIdentity
.scale(scaleFactor)
.translate(-translateX, 0)
.translate(x)
.scale(k)
);
Working Fiddle only for X-Axis here: https://jsfiddle.net/9j4kqq1v/3/
Working fiddle for X and Y-axis here: https://jsfiddle.net/9j4kqq1v/5/
I am working through an animation where I need to make this plane follow the path given and appear to be "circling" the earth. Here is the codePen, which as you can see is fairly simple.
My problem is with angles, I am trying to see how much should I rotate the plane as it moves through the path by calculating the slope of two points, and turning it into degrees.
Even though I have added an epsilon to safe-check for consistent differences across the points and every other safe-check, I am getting that as it approaches +-90 degrees, it changes signs, instead of passing to the other quadrant 120 degrees, etc.
I can understand that this is caused by the fact that
You can see this happening in the console right in the mid point (displays: slope, arctangent, degrees).
To solve this, I am recurring to Math.atan2(), by using newPoint.x - firstPoint.x, newPoint.y - firstPoint.y as its arguments. It starts off with the right values (CodePen here). But it still does a funky rotation.
Here is the code (I'm not posting the SVG image because it's very large):
JS
var snap = Snap('#Globe_1_');
// Trail 1
var trail1 = snap.path('M354.3,707.9c13.9,14.5,27.9,27.2,41.7,38c13.9,10.9,27.2,19.3,39.3,25.4 c12.6,6.1,24.8,9,35.7,10.3c10.9,1.2,21.1-1.2,30.2-5.4c17-7.8,29-24.8,35.7-48.3c7.2-23.5,9-55,5.4-91.8 c-3.7-36.8-12-77.9-24.8-120.9c-12.6-43-30.2-87.6-51.9-131.7c-21.1-44.1-45.2-85.8-70.7-122.7s-50.8-69.5-77.3-95.5 c-27.2-26-52.5-43.6-75.6-53.2c-22.9-9.7-43.6-10.3-60.4-2.5c-16.3,7.8-27.9,24.2-35.1,47.7c-7.2,23.5-9.7,53.8-6.6,88.8')
.attr({
id: 'trail1',
fill:'none',
stroke: '#C25353',
strokeMiterLimit: 10
});
var len = trail1.getTotalLength();
var plane1 = snap.path('M375.7,708.4c0.1,0.8-0.7,1.8-1.6,1.9l-10.4,0.2l-8.4,15.1l-4,0l4.1-14.6l-7.8-0.2l-2.7,3.2L342,714 l1.6-4.9l-1.7-5.4l3.1,0.1l2.5,3.6l7.8,0.2l-4.3-14.6l4,0l8.3,14.7l10.4-0.2C375.5,706.7,376,707.1,375.7,708.4z') .attr({fill: '#CDCCCC' });
var initPoint = trail1.getPointAtLength( 1 ),
lastPoint,
slope = 0,
lastLen = 0;
Snap.animate(0, len, function( value ) {
movePoint = trail1.getPointAtLength( value );
if (lastPoint && ( Math.abs(lastPoint.y - movePoint.y) > 1 || Math.abs(lastPoint.x - movePoint.x) > 1 )) {
var slope_val = (lastPoint.y - movePoint.y) / (lastPoint.x - movePoint.x),
slope_atan = Math.atan2(movePoint.x - initPoint.x, movePoint.y - initPoint.y),
slope_deg = Snap.deg(slope_atan);
slope = slope_deg;
console.log('Capturing rotation', slope_val, slope_atan, slope_deg);
lastLen = value;
}
plane1.transform( 't' + parseInt(movePoint.x - 350) + ',' + parseInt( movePoint.y - 700) + 'r' + slope);
lastPoint = movePoint;
}, 5000, mina.linear);
Can you please help me out, thank you
I'm not sure of the full effect you are after, if its purely 2d angle, Snap already has this built in (returning angle from point along line), so no need to work too hard...
element.getPointAtLength() returns an angle alpha, so movePoint.alpha can be used...
relevant line below, and other calculation lines removed.
plane1.transform( 't' + parseInt(movePoint.x - 350) + ',' + parseInt( movePoint.y - 700) + 'r' + (180 + movePoint.alpha));
codepen
I'm using d3.js - I have a pie chart here. The problem though is when the slices are small - the labels overlap. What is the best way of spreading out the labels.
http://jsfiddle.net/BxLHd/16/
Here is the code for the labels. I am curious - is it possible to mock a 3d pie chart with d3?
//draw labels
valueLabels = label_group.selectAll("text.value").data(filteredData)
valueLabels.enter().append("svg:text")
.attr("class", "value")
.attr("transform", function(d) {
return "translate(" + Math.cos(((d.startAngle+d.endAngle - Math.PI)/2)) * (that.r + that.textOffset) + "," + Math.sin((d.startAngle+d.endAngle - Math.PI)/2) * (that.r + that.textOffset) + ")";
})
.attr("dy", function(d){
if ((d.startAngle+d.endAngle)/2 > Math.PI/2 && (d.startAngle+d.endAngle)/2 < Math.PI*1.5 ) {
return 5;
} else {
return -7;
}
})
.attr("text-anchor", function(d){
if ( (d.startAngle+d.endAngle)/2 < Math.PI ){
return "beginning";
} else {
return "end";
}
}).text(function(d){
//if value is greater than threshold show percentage
if(d.value > threshold){
var percentage = (d.value/that.totalOctets)*100;
return percentage.toFixed(2)+"%";
}
});
valueLabels.transition().duration(this.tweenDuration).attrTween("transform", this.textTween);
valueLabels.exit().remove();
As #The Old County discovered, the previous answer I posted fails in firefox because it relies on the SVG method .getIntersectionList() to find conflicts, and that method hasn't been implemented yet in Firefox.
That just means we have to keep track of label positions and test for conflicts ourselves. With d3, the most efficient way to check for layout conflicts involves using a quadtree data structure to store positions, that way you don't have to check every label for overlap, just those in a similar area of the visualization.
The second part of the code from the previous answer gets replaced with:
/* check whether the default position
overlaps any other labels*/
var conflicts = [];
labelLayout.visit(function(node, x1, y1, x2, y2){
//recurse down the tree, adding any overlapping labels
//to the conflicts array
//node is the node in the quadtree,
//node.point is the value that we added to the tree
//x1,y1,x2,y2 are the bounds of the rectangle that
//this node covers
if ( (x1 > d.r + maxLabelWidth/2)
//left edge of node is to the right of right edge of label
||(x2 < d.l - maxLabelWidth/2)
//right edge of node is to the left of left edge of label
||(y1 > d.b + maxLabelHeight/2)
//top (minY) edge of node is greater than the bottom of label
||(y2 < d.t - maxLabelHeight/2 ) )
//bottom (maxY) edge of node is less than the top of label
return true; //don't bother visiting children or checking this node
var p = node.point;
var v = false, h = false;
if ( p ) { //p is defined, i.e., there is a value stored in this node
h = ( ((p.l > d.l) && (p.l <= d.r))
|| ((p.r > d.l) && (p.r <= d.r))
|| ((p.l < d.l)&&(p.r >=d.r) ) ); //horizontal conflict
v = ( ((p.t > d.t) && (p.t <= d.b))
|| ((p.b > d.t) && (p.b <= d.b))
|| ((p.t < d.t)&&(p.b >=d.b) ) ); //vertical conflict
if (h&&v)
conflicts.push(p); //add to conflict list
}
});
if (conflicts.length) {
console.log(d, " conflicts with ", conflicts);
var rightEdge = d3.max(conflicts, function(d2) {
return d2.r;
});
d.l = rightEdge;
d.x = d.l + bbox.width / 2 + 5;
d.r = d.l + bbox.width + 10;
}
else console.log("no conflicts for ", d);
/* add this label to the quadtree, so it will show up as a conflict
for future labels. */
labelLayout.add( d );
var maxLabelWidth = Math.max(maxLabelWidth, bbox.width+10);
var maxLabelHeight = Math.max(maxLabelHeight, bbox.height+10);
Note that I've changed the parameter names for the edges of the label to l/r/b/t (left/right/bottom/top) to keep everything logical in my mind.
Live fiddle here: http://jsfiddle.net/Qh9X5/1249/
An added benefit of doing it this way is that you can check for conflicts based on the final position of the labels, before actually setting the position. Which means that you can use transitions for moving the labels into position after figuring out the positions for all the labels.
Should be possible to do. How exactly you want to do it will depend on what you want to do with spacing out the labels. There is not, however, a built in way of doing this.
The main problem with the labels is that, in your example, they rely on the same data for positioning that you are using for the slices of your pie chart. If you want them to space out more like excel does (i.e. give them room), you'll have to get creative. The information you have is their starting position, their height, and their width.
A really fun (my definition of fun) way to go about solving this would be to create a stochastic solver for an optimal arrangement of labels. You could do this with an energy-based method. Define an energy function where energy increases based on two criteria: distance from start point and overlap with nearby labels. You can do simple gradient descent based on that energy criteria to find a locally optimal solution with regards to your total energy, which would result in your labels being as close as possible to their original points without a significant amount of overlap, and without pushing more points away from their original points.
How much overlap is tolerable would depend on the energy function you specify, which should be tunable to give a good looking distribution of points. Similarly, how much you're willing to budge on point closeness would depend on the shape of your energy increase function for distance from the original point. (A linear energy increase will result in closer points, but greater outliers. A quadratic or a cubic will have greater average distance, but smaller outliers.)
There might also be an analytical way of solving for the minima, but that would be harder. You could probably develop a heuristic for positioning things, which is probably what excel does, but that would be less fun.
One way to check for conflicts is to use the <svg> element's getIntersectionList() method. That method requires you to pass in an SVGRect object (which is different from a <rect> element!), such as the object returned by a graphical element's .getBBox() method.
With those two methods, you can figure out where a label is within the screen and if it overlaps anything. However, one complication is that the rectangle coordinates passed to getIntersectionList are interpretted within the root SVG's coordinates, while the coordinates returned by getBBox are in the local coordinate system. So you also need the method getCTM() (get cumulative transformation matrix) to convert between the two.
I started with the example from Lars Khottof that #TheOldCounty had posted in a comment, as it already included lines between the arc segments and the labels. I did a little re-organization to put the labels, lines and arc segments in separate <g> elements. That avoids strange overlaps (arcs drawn on top of pointer lines) on update, and it also makes it easy to define which elements we're worried about overlapping -- other labels only, not the pointer lines or arcs -- by passing the parent <g> element as the second parameter to getIntersectionList.
The labels are positioned one at a time using an each function, and they have to be actually positioned (i.e., the attribute set to its final value, no transitions) at the time the position is calculated, so that they are in place when getIntersectionList is called for the next label's default position.
The decision of where to move a label if it overlaps a previous label is a complex one, as #ckersch's answer outlines. I keep it simple and just move it to the right of all the overlapped elements. This could cause a problem at the top of the pie, where labels from the last segments could be moved so that they overlap labels from the first segments, but that's unlikely if the pie chart is sorted by segment size.
Here's the key code:
labels.text(function (d) {
// Set the text *first*, so we can query the size
// of the label with .getBBox()
return d.value;
})
.each(function (d, i) {
// Move all calculations into the each function.
// Position values are stored in the data object
// so can be accessed later when drawing the line
/* calculate the position of the center marker */
var a = (d.startAngle + d.endAngle) / 2 ;
//trig functions adjusted to use the angle relative
//to the "12 o'clock" vector:
d.cx = Math.sin(a) * (that.radius - 75);
d.cy = -Math.cos(a) * (that.radius - 75);
/* calculate the default position for the label,
so that the middle of the label is centered in the arc*/
var bbox = this.getBBox();
//bbox.width and bbox.height will
//describe the size of the label text
var labelRadius = that.radius - 20;
d.x = Math.sin(a) * (labelRadius);
d.sx = d.x - bbox.width / 2 - 2;
d.ox = d.x + bbox.width / 2 + 2;
d.y = -Math.cos(a) * (that.radius - 20);
d.sy = d.oy = d.y + 5;
/* check whether the default position
overlaps any other labels*/
//adjust the bbox according to the default position
//AND the transform in effect
var matrix = this.getCTM();
bbox.x = d.x + matrix.e;
bbox.y = d.y + matrix.f;
var conflicts = this.ownerSVGElement
.getIntersectionList(bbox, this.parentNode);
/* clear conflicts */
if (conflicts.length) {
console.log("Conflict for ", d.data, conflicts);
var maxX = d3.max(conflicts, function(node) {
var bb = node.getBBox();
return bb.x + bb.width;
})
d.x = maxX + 13;
d.sx = d.x - bbox.width / 2 - 2;
d.ox = d.x + bbox.width / 2 + 2;
}
/* position this label, so it will show up as a conflict
for future labels. (Unfortunately, you can't use transitions.) */
d3.select(this)
.attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y;
});
});
And here's the working fiddle: http://jsfiddle.net/Qh9X5/1237/
I am using the D3 tree layout, such as this one: http://mbostock.github.com/d3/talk/20111018/tree.html
I have modified it for my needs and am running into an issue. The example has the same issue too where if you have too many nodes open then they become compact and makes reading and interacting difficult. I am wanting to defined a minimum vertical space between nodes while re-sizing stage to allow for such spacing.
I tried modifying the separation algorithm to make it work:
.separation(function (a, b) {
return (a.parent == b.parent ? 1 : 2) / a.depth;
})
That didn't work. I also tried calculating which depth had the most children then telling the height of the stage to be children * spaceBetweenNodes. That got me closer, but still was not accurate.
depthCounts = [];
nodes.forEach(function(d, i) {
d.y = d.depth * 180;
if(!depthCounts[d.depth])
depthCounts[d.depth] = 0;
if(d.children)
{
depthCounts[d.depth] += d.children.length;
}
});
tree_resize(largest_depth(depthCounts) * spaceBetweenNodes);
I also tried to change the node's x value too in the method below where it calculates the y separation, but no cigar. I would post that change too but I removed it from my code.
nodes.forEach(function(d, i) {
d.y = d.depth * 180;
});
If you can suggest a way or know a way that I can accomplish a minimum spacing vertically between nodes please post. I will be very grateful. I am probably missing something very simple.
As of 2016, I was able to achieve this using just
tree.nodeSize([height, width])
https://github.com/mbostock/d3/wiki/Tree-Layout#nodeSize
The API Reference is a bit poor, but is works pretty straight forward. Be sure to use it after tree.size([height, width]) or else you will be overriding your values again.
For more reference: D3 Tree Layout Separation Between Nodes using NodeSize
I was able to figure this out with help from a user on Google Groups. I was not able to find the post. The solution requires you to modify D3.js in one spot, which is not recommended but it was the only to get around this issue that I could find.
Starting around line 5724 or this method: d3_layout_treeVisitAfter
change:
d3_layout_treeVisitAfter(root, function(node) {
node.x = (node.x - x0) / (x1 - x0) * size[0];
node.y = node.depth / y1 * size[1];
delete node._tree;
});
to:
d3_layout_treeVisitAfter(root, function(node) {
// make sure size is null, we will make it null when we create the tree
if(size === undefined || size == null)
{
node.x = (node.x - x0) * elementsize[0];
node.y = node.depth * elementsize[1];
}
else
{
node.x = (node.x - x0) / (x1 - x0) * size[0];
node.y = node.depth / y1 * size[1];
}
delete node._tree;
});
Below add a new variable called: elementsize and default it to [ 1, 1 ] to line 5731
var hierarchy = d3.layout.hierarchy().sort(null).value(null)
, separation = d3_layout_treeSeparation
, elementsize = [ 1, 1 ] // Right here
, size = [ 1, 1 ];
Below that there is a method called tree.size = function(x). Add the following below that definition:
tree.elementsize = function(x) {
if (!arguments.length) return elementsize;
elementsize = x;
return tree;
};
Finally when you create the tree you can change the elementsize like so
var tree = d3.layout.tree()
.size(null)
.elementsize(50, 240);
I know I'm not supposed to respond to other answers, but I don't have enough reputation to add a comment.
Anyway, I just wanted to update this for people using the latest d3.v3.js file. (I assume this is because of a new version, because the line references in the accepted answer were wrong for me.)
The d3.layout.tree function that you are editing is found between lines 6236 and 6345. d3_layout_treeVisitAfter starts on line 6318. The hierarchy variable is declared on line 6237. The bit about tree.elementsize still stands - I put it on line 6343.
Lastly (I assume this was an error): when you create the tree, put the dimensions inside square brackets, like you normally do with "size". So:
var tree = d3.layout.tree()
.size(null)
.elementsize([50, 240]);
The original fix you proposed will work, you just have to make sure you do it after you add everything to the canvas. d3 recalculates the layout each time you enter, exit, append, etc. Once you've done all that, then you can fiddle with the d.y to fix the depth.
nodes.forEach(function(d) { d.y = d.depth * fixdepth});