I developed a javascript component to display a tree, based on D3.js
I'm not sure how clean my code is, I'm not used to code a lot of javascript and I'm constrained to write everything in 1 file.
So I'd take every advice on how to make this code better. And in particular, there's a behaviour I don't understand: a "magical" line of code which shouldn't affect anything, but when removed something is broken.
Here's the whole code:
ApplicationMap = function(vaadinConnector, element) {
var self = this;
this.vaadinConnector = vaadinConnector;
self.root = {};
var red = "#f5696d";
var green = "#40bc96";
var orange = "#fabd57";
var grey = "#e7e7e7";
var statusColor = function(status) {
if (status == "SUCCESS") {
return green;
} else if (status == "WARNING") {
return orange;
} else if (status == "CRITICAL") {
return red;
} else {
return grey;
}
};
var domAttach = d3.select(element);
var nodeWidth = 100;
var nodeHeight = 70;
var tree = d3.layout.tree()
.nodeSize([nodeWidth + 10, nodeHeight + 20])
.separation(function (a, b) {
return (a.parent == b.parent ? 1.5 : 2.5);
});
var diagonal = d3.svg.diagonal()
.projection(function (d) {
return [d.x, d.y];
});
domAttach.html("");
var cx = parseInt(domAttach.style('width')) / 2;
var cy = 50;
var vis = domAttach.append("svg:svg")
.attr("width", "100%")
.attr("height", "100%")
.append("svg:g");
vis.attr("transform", "translate(" + cx + "," + cy + ")");
// Toggle children
this.toggle = function(d) {
if (d.children) {
d.hiddenChildren = d.children;
d.children = null;
} else {
d.children = d.hiddenChildren;
d.hiddenChildren = null;
}
};
this.toggleAll = function(d) {
if (d.children) {
d.children.forEach(self.toggleAll);
self.toggle(d);
}
};
var update = function(source) {
var duration = d3.event && d3.event.altKey ? 5000 : 500;
// Compute the new tree layout.
var nodes = tree.nodes(self.root).reverse();
// Update the nodes…
var node = vis.selectAll("g.node")
.data(nodes, function (d) {
return d.id;
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.attr("transform", function () {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on("click", function (d) {
self.vaadinConnector.onClickNode(d.name);
})
.on("mouseover", function () {
d3.select(this).classed("hover", true);
})
.on("mouseout", function () {
d3.select(this).classed("hover", false);
});
nodeEnter.append("svg:rect")
.attr("width", nodeWidth)
.attr("height", nodeHeight)
.attr("x", -nodeWidth / 2)
.attr("y", 7 - nodeHeight / 2)
.style("fill-opacity", 1e-6)
.style("fill", function (d) {
return statusColor(d.status);
});
nodeEnter.append("svg:text")
.attr("text-anchor", "middle")
.attr("y", -5)
.classed("pointed", true)
.text(function (d) {
return d.name;
})
.style("fill-opacity", 1e-6);
// Expand / collapse icon
var filtered = nodeEnter.filter(function(d) {
return (d.children && d.children.length > 0) || (d.hiddenChildren && d.hiddenChildren.length > 0);
}).append("svg:text")
.attr("text-anchor", "right")
.attr("x", 34)
.attr("y", 35)
.classed({
"toggle-expand": true,
"pointed": true,
"faw": true
})
.style("fill-opacity", 1e-6)
.on("click", function(d) {
d3.event.stopPropagation();
self.toggle(d);
update(d);
d3.select(this).text(function(d) {
return (d.children && d.children.length > 0) ? "\uf150" : "\uf151";
});
});
filtered.text(function(d) {
return (d.children && d.children.length > 0) ? "\uf150" : "\uf151";
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.selectAll("*")
.style("fill-opacity", 1);
nodeUpdate.select("rect")
.style("fill", function(d) {
return statusColor(d.status);
});
// <magic>
// nodeUpdate.select(".toggle-expand");
// </magic>
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function () {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
nodeExit.selectAll("*")
.style("fill-opacity", 1e-6);
nodeExit.selectAll("circle")
.style("stroke-opacity", 1e-6);
// Stash the old positions for transition.
nodes.forEach(function (d) {
d.x0 = d.x;
d.y0 = d.y;
});
};
this.updateRoot = function(strRoot) {
self.root = JSON.parse(strRoot);
self.root.x0 = 0;
self.root.y0 = 0;
update(self.root);
}
};
You can see it in action here: http://jsfiddle.net/jg0h24hr/1/
As you may notice on jsfiddle, if you wait 3 seconds the tree is updated but then, the node won't expand / reduce anymore when you click on their bottom right icon. However if I uncomment the "magical" line of code (line 154 on jsfiddle), then it works correctly. So I suspect either a misconception in my code, or maybe a bug in D3.js? Any idea about that?
Maybe there's some underlying invalid cached data in D3, that gets refreshed when calling d3.select ?