I am attempting to make a tree viewing web application. I am using the d3 tree layout to generate my trees. What I would like to be able to do is scale the size of the tree in either axis. In otherwords being able to scale the x and y axis of the viewer. The would effectively just increase either the vertical distance between nodes or increase the length of the branches.
I have been unable to accomplish this. I have attempted to change the nodeSize() of the tree layout, and then update which had no effect. I have also attempted to modify the x or y coordinate of each node which again had no effect. I have been completely unable to modify the physical layout of the tree once it has been rendered.
I have read and attempted the fixes in the following threads:
- Dynamically resize the d3 tree layout based on number of childnodes
- D3 Tree Layout Separation Between Nodes using NodeSize
But they did not solve my problem.
I am implementing a TreeGenerator object, which handles all of the rendering of the SVG, using prototyped functions. I am going for a full screen feeling, so the TreeGenerator appends an SVG to a div with width: 100%; height: 100%
. I should node that I have implemented both zooming and panning functionality.
Here is the constructor:
TreeGenerator = function(target, treeString) {
// Set up everything here
// colors
this.nodeColor = "#3B6073";
this.highlightColor = "#F22248";
this.searchColor = "#0DFFC2";
this.nodeHeight = 5;
this.nodeLength = 20;
this.zoomX = 1;
this.zoomY = 5;
this._winHeight = window.innerHeight;
this._winWidth = window.innerWidth;
//this.xScale = d3.scale.linear().domain([0,100]).range([0, this._winWidth])
//this.yScale = d3.scale.linear().domain([0,100]).range([0, this._winHeight])
// keep the matching results from last search
this._searchResults = [];
// path lengths
this.maxPathLength = 0;
this._loadSVG(target);
this._tree(treeString);
}
And here are the two functions _loadSVG and _tree which are called from the constructor:
TreeGenerator.prototype._loadSVG = function(target) {
var zoom = d3.behavior.zoom()
//.size([this._winWidth, this._winHeight])
.scaleExtent([this.zoomX,this.zoomY])
//.x(this.xScale)
//.y(this.yScale)
//.center([height/2, width/2])
.on("zoom", zoomed);
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
this.svg = d3.select(target, ":first-child").append("svg")
.append("g")
//.attr("transform", "translate("+width / 2+","+height / 2+")")
.call(zoom)
.on("dblclick.zoom", null)
var rect = this.svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.style("fill", "none")
.style("pointer-events", "all");
this.container = this.svg.append("g")
// scope it for d3 funcs
var container = this.container
var self = this;
function dottype(d) {
d.x = +d.x;
d.y = +d.y;
return d;
}
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
}
And _tree:
TreeGenerator.prototype._tree = function (treeString) {
// needs an array for d3
this.treeData = this.parseTree(treeString);
this.root = this.treeData[0];
// set up the layout
this.tree = d3.layout.tree()
.nodeSize([this.nodeHeight,this.nodeLength]);
// set path dists
this._setPathDist();
this.yScale = d3.scale.linear().domain([0, this.maxPathLength]).range([0, 20]);
this.xScale = d3.scale.linear().domain([1,100]).range([10, 30]);
var self = this;
update(this.root);
function update(source) {
var i = 0;
// generator for paths from
// node ---> node
var horizontal = d3.svg.line().interpolate('step-before')
.x(function (d) { return d.x; })
.y(function (d) { return d.y; });
// Compute the new tree layout.
var nodes = self.tree.nodes(source).reverse()
nodes.forEach(function (d) {
if(!d.pathLength == 0) {
d.y = d.y * self.yScale(d.pathLength);
}
else if(d.children != undefined) {
d.y += 5;
}
});
links = self.tree.links(nodes);
// Declare the nodes
var node = self.container.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); })
// Enter the nodes.
doubleclickTimer = false // dont ask
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("id", function(d) { return "node-"+d.id.toString(); })
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")"; })
.on("click", clicked);
nodeEnter.append("circle")
.attr("r", 1)
.style("fill", self.nodeColor)
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -1 : 1; })
.attr("y", function(d) {
return d.children == undefined ? 1 : -1;
})
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1);
// try and update
//var nodeUpdate = node.update()
// .attr("x", function(d) { return d.x });
// Declare the links
var link = self.container.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
return horizontal([
{y: d.source.x, x: d.source.y},
{y: d.target.x, x: d.target.y}
]);
});
function clicked(d) {
// need the g group for highlight
node = this;
// if double click timer is active, this click is the double click
if ( doubleclickTimer )
{
clearTimeout(doubleclickTimer);
doubleclickTimer = false;
collapse(d);
}
// otherwise, what to do after single click (double click has timed out)
else {
doubleclickTimer = setTimeout( function() {
doubleclickTimer = false;
highlight(node, d);
}, 175);
}
}
function highlight(node,d) {
// we want to bold the text
// and color the node
self._unhighlightNode();
// toggle if clicking again
if(self._highlightedNode == node) {
self._highlightedNode = undefined;
self.selectedNode = undefined;
return;
}
// set the new one
var circle = d3.select(node).select("circle");
var text = d3.select(node).select("text");
circle.style("fill", self.highlightColor);
text.style("font-size","4px");
// update the pointer
self._highlightedNode = node;
self.selectedNode = d;
}
function collapse(d) {
}
}
};
I know the code is a bit messy, but it is just full of commented out things I did in an attempt to fix this issue. I appreciate any help that can be offered. Hopefully I included enough info.